From ce4224dba8aae810967785e2bf7c0f56076a6beb Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Wed, 23 Nov 2022 13:50:55 -0500 Subject: [PATCH 001/200] feat: Added ODP Segment Manager Implementation (#790) * ODP Segment Manager Implementation * Refactor & Clean Up to plugins/odp * Address PR comments * Refactor & Clean Up based on extended discussion & comments * Refactor Ctd --- .../core/odp/lru_cache/CacheElement.tests.ts | 53 --- .../lib/core/odp/lru_cache/LRUCache.tests.ts | 307 ----------------- .../lib/core/odp/lru_cache/LRUCache.ts | 118 ------- .../lib/{plugins => core}/odp/odp_config.ts | 0 .../lib/{plugins => core}/odp/odp_event.ts | 0 .../odp/odp_event_api_manager.ts} | 4 +- .../odp/odp_event_manager.ts | 46 +-- .../odp/odp_response_schema.ts | 0 .../odp/odp_segment_api_manager.ts} | 51 ++- .../lib/core/odp/odp_segment_manager.ts | 116 +++++++ .../lib/{plugins => core}/odp/odp_types.ts | 0 .../lib/core/odp/optimizely_segment_option.ts | 21 ++ .../optimizely-sdk/lib/utils/enums/index.ts | 101 +++--- .../lru_cache/browser_lru_cache.ts} | 15 +- .../utils/lru_cache/cache_element.tests.ts | 53 +++ .../lru_cache/cache_element.ts} | 30 +- .../lib/utils/lru_cache/index.ts | 21 ++ .../lib/utils/lru_cache/lru_cache.tests.ts | 309 ++++++++++++++++++ .../lib/utils/lru_cache/lru_cache.ts | 108 ++++++ .../lib/utils/lru_cache/server_lru_cache.ts | 26 ++ ...ger.spec.ts => odpEventApiManager.spec.ts} | 34 +- .../tests/odpEventManager.spec.ts | 201 ++++++++---- ...anager.spec.ts => odpSegmentApiManager.ts} | 72 ++-- .../tests/odpSegmentManager.spec.ts | 138 ++++++++ 24 files changed, 1132 insertions(+), 692 deletions(-) delete mode 100644 packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.tests.ts delete mode 100644 packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.tests.ts delete mode 100644 packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.ts rename packages/optimizely-sdk/lib/{plugins => core}/odp/odp_config.ts (100%) rename packages/optimizely-sdk/lib/{plugins => core}/odp/odp_event.ts (100%) rename packages/optimizely-sdk/lib/{plugins/odp/rest_api_manager.ts => core/odp/odp_event_api_manager.ts} (96%) rename packages/optimizely-sdk/lib/{plugins => core}/odp/odp_event_manager.ts (92%) rename packages/optimizely-sdk/lib/{plugins => core}/odp/odp_response_schema.ts (100%) rename packages/optimizely-sdk/lib/{plugins/odp/graphql_manager.ts => core/odp/odp_segment_api_manager.ts} (82%) create mode 100644 packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts rename packages/optimizely-sdk/lib/{plugins => core}/odp/odp_types.ts (100%) create mode 100644 packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts rename packages/optimizely-sdk/lib/{core/odp/lru_cache/index.ts => utils/lru_cache/browser_lru_cache.ts} (74%) create mode 100644 packages/optimizely-sdk/lib/utils/lru_cache/cache_element.tests.ts rename packages/optimizely-sdk/lib/{core/odp/lru_cache/CacheElement.ts => utils/lru_cache/cache_element.ts} (62%) create mode 100644 packages/optimizely-sdk/lib/utils/lru_cache/index.ts create mode 100644 packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.tests.ts create mode 100644 packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts create mode 100644 packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts rename packages/optimizely-sdk/tests/{restApiManager.spec.ts => odpEventApiManager.spec.ts} (83%) rename packages/optimizely-sdk/tests/{graphQlManager.spec.ts => odpSegmentApiManager.ts} (83%) create mode 100644 packages/optimizely-sdk/tests/odpSegmentManager.spec.ts diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.tests.ts b/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.tests.ts deleted file mode 100644 index 728026c7f..000000000 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.tests.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assert } from 'chai' -import { CacheElement } from './CacheElement'; - -const sleep = async (ms: number) => { - return await new Promise(r => setTimeout(r, ms)) -} - -describe('/odp/lru_cache/CacheElement', () => { - let element: CacheElement - - beforeEach(() => { - element = new CacheElement('foo') - }) - - it('should initialize a valid CacheElement', () => { - assert.exists(element) - assert.equal(element.value, 'foo') - assert.isNotNull(element.time) - assert.doesNotThrow(() => element.is_stale(0)) - }) - - it('should return false if not stale based on timeout', () => { - const timeoutLong = 1000 - assert.equal(element.is_stale(timeoutLong), false) - }) - - it('should return false if not stale because timeout is less than or equal to 0', () => { - const timeoutNone = 0 - assert.equal(element.is_stale(timeoutNone), false) - }) - - it('should return true if stale based on timeout', async () => { - await sleep(100) - const timeoutShort = 1 - assert.equal(element.is_stale(timeoutShort), true) - }) -}) \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.tests.ts b/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.tests.ts deleted file mode 100644 index 9f888e598..000000000 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.tests.ts +++ /dev/null @@ -1,307 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assert } from 'chai' -import { LRUCache, ClientLRUCache, ServerLRUCache } from './LRUCache' - -const sleep = async (ms: number) => { - return await new Promise(r => setTimeout(r, ms)) -} - -describe('/lib/core/odp/lru_cache (Default)', () => { - let cache: LRUCache; - - describe('LRU Cache > Initialization', () => { - it('should successfully create a new cache with maxSize > 0 and timeout > 0', () => { - cache = new LRUCache({ - maxSize: 1000, - timeout: 2000 - }) - - assert.exists(cache) - - assert.equal(cache.maxSize, 1000) - assert.equal(cache.timeout, 2000) - }) - - it('should successfully create a new cache with maxSize == 0 and timeout == 0', () => { - cache = new LRUCache({ - maxSize: 0, - timeout: 0 - }) - - assert.exists(cache) - - assert.equal(cache.maxSize, 0) - assert.equal(cache.timeout, 0) - }) - }) - - describe('LRU Cache > Save & Lookup', () => { - const maxCacheSize = 2 - - beforeEach(() => { - cache = new LRUCache({ - maxSize: maxCacheSize, - timeout: 1000 - }) - }) - - it('should have no values in the cache upon initialization', () => { - assert.isNull(cache.peek(1)) - }) - - it('should save keys and values of any valid type', () => { - cache.save({ key: 'a', value: 1 }) // { a: 1 } - assert.equal(cache.peek('a'), 1) - - cache.save({ key: 2, value: 'b' }) // { a: 1, 2: 'b' } - assert.equal(cache.peek(2), 'b') - - const foo = Symbol('foo') - const bar = {} - cache.save({ key: foo, value: bar }) // { 2: 'b', Symbol('foo'): {} } - assert.deepEqual({}, cache.peek(foo)) - }) - - it('should save values up to its maxSize', () => { - cache.save({ key: 'a', value: 1 }) // { a: 1 } - assert.equal(cache.peek('a'), 1) - - cache.save({ key: 'b', value: 2 }) // { a: 1, b: 2 } - assert.equal(cache.peek('a'), 1) - assert.equal(cache.peek('b'), 2) - - cache.save({ key: 'c', value: 3 }) // { b: 2, c: 3 } - assert.equal(cache.peek('a'), null) - assert.equal(cache.peek('b'), 2) - assert.equal(cache.peek('c'), 3) - }) - - it('should override values of matching keys when saving', () => { - cache.save({ key: 'a', value: 1 }) // { a: 1 } - assert.equal(cache.peek('a'), 1) - - cache.save({ key: 'a', value: 2 }) // { a: 2 } - assert.equal(cache.peek('a'), 2) - - cache.save({ key: 'a', value: 3 }) // { a: 3 } - assert.equal(cache.peek('a'), 3) - }) - - it('should update cache accordingly when using lookup/peek', () => { - assert.isNull(cache.lookup(3)) - - cache.save({ key: 'b', value: 201 }) // { b: 201 } - cache.save({ key: 'a', value: 101 }) // { b: 201, a: 101 } - - assert.equal(cache.lookup('b'), 201) // { a: 101, b: 201 } - - cache.save({ key: 'c', value: 302 }) // { b: 201, c: 302 } - - assert.isNull(cache.peek(1)) - assert.equal(cache.peek('b'), 201) - assert.equal(cache.peek('c'), 302) - assert.equal(cache.lookup('c'), 302) // { b: 201, c: 302 } - - cache.save({ key: 'a', value: 103 }) // { c: 302, a: 103 } - assert.equal(cache.peek('a'), 103) - assert.isNull(cache.peek('b')) - assert.equal(cache.peek('c'), 302) - }) - }) - - describe('LRU Cache > Size', () => { - it('should keep LRU Cache map size capped at cache.capacity', () => { - const maxCacheSize = 2 - - cache = new LRUCache({ - maxSize: maxCacheSize, - timeout: 1000 - }) - - cache.save({ key: 'a', value: 1 }) // { a: 1 } - cache.save({ key: 'b', value: 2 }) // { a: 1, b: 2 } - - assert.equal(cache.map.size, maxCacheSize) - assert.equal(cache.map.size, cache.maxSize) - }) - - it('should not save to cache if maxSize is 0', () => { - cache = new LRUCache({ - maxSize: 0, - timeout: 1000 - }) - - assert.isNull(cache.lookup('a')) - cache.save({ key: 'a', value: 100 }) - assert.isNull(cache.lookup('a')) - }) - - it('should not save to cache if maxSize is negative', () => { - cache = new LRUCache({ - maxSize: -500, - timeout: 1000 - }) - - assert.isNull(cache.lookup('a')) - cache.save({ key: 'a', value: 100 }) - assert.isNull(cache.lookup('a')) - }) - }) - - describe('LRU Cache > Timeout', () => { - it('should discard stale entries in the cache on peek/lookup when timeout is greater than 0', async () => { - const maxTimeout = 100 - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout - }) - - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - cache.save({ key: 'c', value: 300 }) // { a: 100, b: 200, c: 300 } - - assert.equal(cache.peek('a'), 100) - assert.equal(cache.peek('b'), 200) - assert.equal(cache.peek('c'), 300) - - await sleep(150) - - assert.isNull(cache.lookup('a')) - assert.isNull(cache.lookup('b')) - assert.isNull(cache.lookup('c')) - - cache.save({ key: 'd', value: 400 }) // { d: 400 } - cache.save({ key: 'a', value: 101 }) // { d: 400, a: 101 } - - assert.equal(cache.lookup('a'), 101) // { d: 400, a: 101 } - assert.equal(cache.lookup('d'), 400) // { a: 101, d: 400 } - }) - - it('should never have stale entries if timeout is 0', async () => { - const maxTimeout = 0 - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout - }) - - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - - await sleep(100) - assert.equal(cache.lookup('a'), 100) - assert.equal(cache.lookup('b'), 200) - }) - - it('should never have stale entries if timeout is less than 0', async () => { - const maxTimeout = -500 - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout - }) - - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - - await sleep(100) - assert.equal(cache.lookup('a'), 100) - assert.equal(cache.lookup('b'), 200) - }) - }) - - describe('LRU Cache > Reset', () => { - it('should be able to reset the cache', async () => { - cache = new LRUCache({ maxSize: 2, timeout: 100 }) - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - - await sleep(0) - - assert.equal(cache.map.size, 2) - cache.reset() // { } - - await sleep(150) - - assert.equal(cache.map.size, 0) - - it('should be fully functional after resetting the cache', () => { - cache.save({ key: 'c', value: 300 }) // { c: 300 } - cache.save({ key: 'd', value: 400 }) // { c: 300, d: 400 } - assert.isNull(cache.peek('b')) - assert.equal(cache.peek('c'), 300) - assert.equal(cache.peek('d'), 400) - - cache.save({ key: 'a', value: 500 }) // { d: 400, a: 500 } - cache.save({ key: 'b', value: 600 }) // { a: 500, b: 600 } - assert.isNull(cache.peek('c')) - assert.equal(cache.peek('a'), 500) - assert.equal(cache.peek('b'), 600) - - const _ = cache.lookup('a') // { b: 600, a: 500 } - assert.equal(500, _) - - cache.save({ key: 'c', value: 700 }) // { a: 500, c: 700 } - assert.isNull(cache.peek('b')) - assert.equal(cache.peek('a'), 500) - assert.equal(cache.peek('c'), 700) - }) - }) - }) -}) - -describe('/lib/core/odp/lru_cache (Client)', () => { - let cache: ClientLRUCache; - - it('should create and test the default client LRU Cache', () => { - cache = new ClientLRUCache() - assert.exists(cache) - assert.isNull(cache.lookup('a')) - assert.equal(cache.maxSize, 100) - assert.equal(cache.timeout, 600 * 1000) - - cache.save({ key: 'a', value: 100 }) - cache.save({ key: 'b', value: 200 }) - cache.save({ key: 'c', value: 300 }) - assert.equal(cache.map.size, 3) - assert.equal(cache.peek('a'), 100) - assert.equal(cache.lookup('b'), 200) - assert.deepEqual(cache.map.keys().next().value, 'a') - }) -}) - -describe('/lib/core/odp/lru_cache (Server)', () => { - let cache: ServerLRUCache; - - it('should create and test the default server LRU Cache', () => { - cache = new ServerLRUCache() - assert.exists(cache) - assert.isNull(cache.lookup('a')) - assert.equal(cache.maxSize, 10000) - assert.equal(cache.timeout, 600 * 1000) - - cache.save({ key: 'a', value: 100 }) - cache.save({ key: 'b', value: 200 }) - cache.save({ key: 'c', value: 300 }) - assert.equal(cache.map.size, 3) - assert.equal(cache.peek('a'), 100) - assert.equal(cache.lookup('b'), 200) - assert.deepEqual(cache.map.keys().next().value, 'a') - }) -}) diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.ts b/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.ts deleted file mode 100644 index 83d474365..000000000 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import CacheElement from "./CacheElement" - -/** - * Least-Recently Used Cache (LRU Cache) Implementation with Generic Key-Value Pairs - * Analogous to a Map that has a specified max size and a timeout per element. - * - Removes the least-recently used element from the cache if max size exceeded. - * - Removes stale elements (entries older than their timeout) from the cache. - */ -export class LRUCache { - private _map: Map> = new Map() - private _maxSize // Defines maximum size of _map - private _timeout // Milliseconds each entry has before it becomes stale - - get map(): Map> { return this._map } - get maxSize(): number { return this._maxSize } - get timeout(): number { return this._timeout } - - constructor({ maxSize, timeout }: { maxSize: number, timeout: number }) { - this._maxSize = maxSize - this._timeout = timeout - } - - /** - * Returns a valid, non-stale value from LRU Cache based on an input key. - * Additionally moves the element to the end of the cache and removes from cache if stale. - */ - public lookup(key: K): V | null { - if (this._maxSize <= 0) { return null } - - const element: CacheElement | undefined = this._map.get(key) - - if (!element) return null - - if (element.is_stale(this._timeout)) { - this._map.delete(key) - return null - } - - this._map.delete(key) - this._map.set(key, element) - - return element.value - } - - /** - * Inserts/moves an input key-value pair to the end of the LRU Cache. - * Removes the least-recently used element if the cache exceeds it's maxSize. - */ - public save({ key, value }: { key: K, value: V }): void { - if (this._maxSize <= 0) return - - const element: CacheElement | undefined = this._map.get(key) - if (element) this._map.delete(key) - this._map.set(key, new CacheElement(value)) - - if (this._map.size > this._maxSize) { - const firstMapEntryKey = this._map.keys().next().value - this._map.delete(firstMapEntryKey) - } - } - - /** - * Clears the LRU Cache - */ - public reset(): void { - if (this._maxSize <= 0) return - - this._map.clear() - } - - /** - * Reads value from specified key without moving elements in the LRU Cache. - * @param {K} key - */ - public peek(key: K): V | null { - if (this._maxSize <= 0) return null - - const element: CacheElement | undefined = this._map.get(key) - - return element?.value ?? null - } -} - -export class ClientLRUCache extends LRUCache { - constructor() { - super({ - maxSize: 100, - timeout: 600 * 1000 // 600 secs - }) - } -} - -export class ServerLRUCache extends LRUCache { - constructor() { - super({ - maxSize: 10000, - timeout: 600 * 1000 // 600 secs - }) - } -} - -export default LRUCache \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_config.ts b/packages/optimizely-sdk/lib/core/odp/odp_config.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/odp_config.ts rename to packages/optimizely-sdk/lib/core/odp/odp_config.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_event.ts b/packages/optimizely-sdk/lib/core/odp/odp_event.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/odp_event.ts rename to packages/optimizely-sdk/lib/core/odp/odp_event.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp/rest_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts similarity index 96% rename from packages/optimizely-sdk/lib/plugins/odp/rest_api_manager.ts rename to packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts index 8a58de202..10f18ce32 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/rest_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts @@ -23,14 +23,14 @@ const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; /** * Manager for communicating with the Optimizely Data Platform REST API */ -export interface IRestApiManager { +export interface IOdpEventApiManager { sendEvents(apiKey: string, apiHost: string, events: OdpEvent[]): Promise; } /** * Concrete implementation for accessing the ODP REST API */ -export class RestApiManager implements IRestApiManager { +export class OdpEventApiManager implements IOdpEventApiManager { private readonly logger: LogHandler; private readonly requestHandler: RequestHandler; diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts similarity index 92% rename from packages/optimizely-sdk/lib/plugins/odp/odp_event_manager.ts rename to packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 766d5fa0e..8ac7bb041 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -19,7 +19,7 @@ import { OdpEvent } from './odp_event'; import { uuid } from '../../utils/fns'; import { ODP_USER_KEY } from '../../utils/enums'; import { OdpConfig } from './odp_config'; -import { RestApiManager } from './rest_api_manager'; +import { OdpEventApiManager } from './odp_event_api_manager'; const MAX_RETRIES = 3; const DEFAULT_BATCH_SIZE = 10; @@ -80,7 +80,7 @@ export class OdpEventManager implements IOdpEventManager { * REST API Manager used to send the events * @private */ - private readonly apiManager: RestApiManager; + private readonly apiManager: OdpEventApiManager; /** * Handler for recording execution logs * @private @@ -113,23 +113,23 @@ export class OdpEventManager implements IOdpEventManager { private readonly clientVersion: string; public constructor({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - queueSize, - batchSize, - flushInterval, - }: { - odpConfig: OdpConfig, - apiManager: RestApiManager, - logger: LogHandler, - clientEngine: string, - clientVersion: string, - queueSize?: number, - batchSize?: number, - flushInterval?: number + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + queueSize, + batchSize, + flushInterval, + }: { + odpConfig: OdpConfig; + apiManager: OdpEventApiManager; + logger: LogHandler; + clientEngine: string; + clientVersion: string; + queueSize?: number; + batchSize?: number; + flushInterval?: number; }) { this.odpConfig = odpConfig; this.apiManager = apiManager; @@ -231,7 +231,11 @@ export class OdpEventManager implements IOdpEventManager { } if (this.queue.length >= this.queueSize) { - this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', this.queue.length); + this.logger.log( + LogLevel.WARNING, + 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', + this.queue.length + ); return; } @@ -379,7 +383,7 @@ export class OdpEventManager implements IOdpEventManager { private invalidDataFound(data: Map): boolean { const validTypes: string[] = ['string', 'number', 'boolean']; let foundInvalidValue = false; - data.forEach((value) => { + data.forEach(value => { if (!validTypes.includes(typeof value) && value !== null) { foundInvalidValue = true; } diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_response_schema.ts b/packages/optimizely-sdk/lib/core/odp/odp_response_schema.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/odp_response_schema.ts rename to packages/optimizely-sdk/lib/core/odp/odp_response_schema.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp/graphql_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts similarity index 82% rename from packages/optimizely-sdk/lib/plugins/odp/graphql_manager.ts rename to packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts index 12d8fbc8f..aa21b96b5 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/graphql_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts @@ -41,14 +41,20 @@ const AUDIENCE_FETCH_FAILURE_MESSAGE = 'Audience segments fetch failed'; /** * Manager for communicating with the Optimizely Data Platform GraphQL endpoint */ -export interface IGraphQLManager { - fetchSegments(apiKey: string, apiHost: string, userKey: string, userValue: string, segmentsToCheck: string[]): Promise; +export interface IOdpSegmentApiManager { + fetchSegments( + apiKey: string, + apiHost: string, + userKey: string, + userValue: string, + segmentsToCheck: string[] + ): Promise; } /** * Concrete implementation for communicating with the ODP GraphQL endpoint */ -export class GraphQLManager implements IGraphQLManager { +export class OdpSegmentApiManager implements IOdpSegmentApiManager { private readonly logger: LogHandler; private readonly requestHandler: RequestHandler; @@ -70,7 +76,13 @@ export class GraphQLManager implements IGraphQLManager { * @param userValue Associated value to query for the user key * @param segmentsToCheck Audience segments to check for experiment inclusion */ - public async fetchSegments(apiKey: string, apiHost: string, userKey: ODP_USER_KEY, userValue: string, segmentsToCheck: string[]): Promise { + public async fetchSegments( + apiKey: string, + apiHost: string, + userKey: ODP_USER_KEY, + userValue: string, + segmentsToCheck: string[] + ): Promise { if (!apiKey || !apiHost) { this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (Parameters apiKey or apiHost invalid)`); return null; @@ -96,7 +108,7 @@ export class GraphQLManager implements IGraphQLManager { } if (parsedSegments.errors?.length > 0) { - const errors = parsedSegments.errors.map((e) => e.message).join('; '); + const errors = parsedSegments.errors.map(e => e.message).join('; '); this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (${errors})`); @@ -116,16 +128,17 @@ export class GraphQLManager implements IGraphQLManager { * Converts the query parameters to a GraphQL JSON payload * @returns GraphQL JSON string */ - private toGraphQLJson = (userKey: string, userValue: string, segmentsToCheck: string[]): string => ([ - '{"query" : "query {customer"', - `(${userKey} : "${userValue}") `, - '{audiences', - '(subset: [', - ...segmentsToCheck?.map((segment, index) => - `\\"${segment}\\"${index < segmentsToCheck.length - 1 ? ',' : ''}`, - ) || '', - '] {edges {node {name state}}}}}"}', - ].join('')); + private toGraphQLJson = (userKey: string, userValue: string, segmentsToCheck: string[]): string => + [ + '{"query" : "query {customer"', + `(${userKey} : "${userValue}") `, + '{audiences', + '(subset: [', + ...(segmentsToCheck?.map( + (segment, index) => `\\"${segment}\\"${index < segmentsToCheck.length - 1 ? ',' : ''}` + ) || ''), + '] {edges {node {name state}}}}}"}', + ].join(''); /** * Handler for querying the ODP GraphQL endpoint @@ -136,7 +149,13 @@ export class GraphQLManager implements IGraphQLManager { * @param query GraphQL formatted query string * @returns JSON response string from ODP or null */ - private async querySegments(apiKey: string, endpoint: string, userKey: string, userValue: string, query: string): Promise { + private async querySegments( + apiKey: string, + endpoint: string, + userKey: string, + userValue: string, + query: string + ): Promise { const method = 'POST'; const url = endpoint; const headers = { diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts new file mode 100644 index 000000000..b3d86d354 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; +import { LRUCache } from '../../utils/lru_cache'; +import { OdpSegmentApiManager } from './odp_segment_api_manager'; +import { OdpConfig } from './odp_config'; +import { OptimizelySegmentOption } from './optimizely_segment_option'; + +// Schedules connections to ODP for audience segmentation and caches the results. +export class OdpSegmentManager { + odpConfig: OdpConfig; + segmentsCache: LRUCache>; + odpSegmentApiManager: OdpSegmentApiManager; + logger: LogHandler; + + constructor( + odpConfig: OdpConfig, + segmentsCache: LRUCache>, + odpSegmentApiManager: OdpSegmentApiManager, + logger?: LogHandler + ) { + this.odpConfig = odpConfig; + this.segmentsCache = segmentsCache; + this.odpSegmentApiManager = odpSegmentApiManager; + this.logger = logger || getLogger('OdpSegmentManager'); + } + + /** + * Attempts to fetch and return a list of a user's qualified segments from the local segments cache. + * If no cached data exists for the target user, this fetches and caches data from the ODP server instead. + * @param userKey Key used for identifying the id type. + * @param userValue The id value itself. + * @param options An array of OptimizelySegmentOption used to ignore and/or reset the cache. + * @returns Qualified segments for the user from the cache or the ODP server if the cache is empty. + */ + async fetchQualifiedSegments( + userKey: ODP_USER_KEY, + userValue: string, + options: Array + ): Promise | null> { + const { apiHost: odpApiHost, apiKey: odpApiKey } = this.odpConfig; + + if (!odpApiKey || !odpApiHost) { + this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER); + return null; + } + + const segmentsToCheck = this.odpConfig.segmentsToCheck; + if (!segmentsToCheck || segmentsToCheck.length <= 0) { + this.logger.log(LogLevel.DEBUG, 'No segments are used in the project. Returning an empty list.'); + return []; + } + + const cacheKey = this.makeCacheKey(userKey, userValue); + + const ignoreCache = options.includes(OptimizelySegmentOption.IGNORE_CACHE); + const resetCache = options.includes(OptimizelySegmentOption.RESET_CACHE); + + if (resetCache) this.reset(); + + if (!ignoreCache && !resetCache) { + const cachedSegments = this.segmentsCache.lookup(cacheKey); + if (cachedSegments) { + this.logger.log(LogLevel.DEBUG, 'ODP cache hit. Returning segments from cache "%s".', cacheKey); + return cachedSegments; + } + this.logger.log(LogLevel.DEBUG, `ODP cache miss.`); + } + + this.logger.log(LogLevel.DEBUG, `Making a call to ODP server.`); + + const segments = await this.odpSegmentApiManager.fetchSegments( + odpApiKey, + odpApiHost, + userKey, + userValue, + segmentsToCheck + ); + + if (segments && !ignoreCache) this.segmentsCache.save({ key: cacheKey, value: segments }); + + return segments; + } + + /** + * Clears the segments cache + */ + reset(): void { + this.segmentsCache.reset(); + } + + /** + * Creates a key used to identify which user fetchQualifiedSegments should lookup and save to in the segments cache + * @param userKey User type based on ODP_USER_KEY, such as "vuid" or "fs_user_id" + * @param userValue Arbitrary string, such as "test-user" + * @returns Concatenates inputs and returns the string "{userKey}-$-{userValue}" + */ + makeCacheKey(userKey: string, userValue: string): string { + return `${userKey}-$-${userValue}`; + } +} diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_types.ts b/packages/optimizely-sdk/lib/core/odp/odp_types.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/odp_types.ts rename to packages/optimizely-sdk/lib/core/odp/odp_types.ts diff --git a/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts b/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts new file mode 100644 index 000000000..e9a7d0712 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Options for defining behavior of OdpSegmentManager's caching mechanism when calling fetchSegments() +export enum OptimizelySegmentOption { + IGNORE_CACHE, + RESET_CACHE, +} diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index d00e65b66..d8931acde 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -30,6 +30,7 @@ export const ERROR_MESSAGES = { DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', + FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER: '%s: Audience segments fetch failed. (invalid identifier)', IMPROPERLY_FORMATTED_EXPERIMENT: '%s: Experiment key %s is improperly formatted.', INVALID_ATTRIBUTES: '%s: Provided attributes are in an invalid format.', INVALID_BUCKETING_ID: '%s: Unable to generate hash for bucketing ID %s: %s', @@ -116,10 +117,14 @@ export const LOG_MESSAGES = { USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE: '%s: User %s does not meet conditions for targeting rule %s.', USER_MEETS_CONDITIONS_FOR_TARGETING_RULE: '%s: User %s meets conditions for targeting rule %s.', USER_HAS_VARIATION: '%s: User %s is in variation %s of experiment %s.', - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED: 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED: 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID: 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID: 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.', + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED: + 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED: + 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.', + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID: + 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID: + 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.', USER_HAS_FORCED_VARIATION: '%s: Variation %s is mapped to experiment %s and user %s in the forced variation map.', USER_HAS_NO_VARIATION: '%s: User %s is in no variation of experiment %s.', USER_HAS_NO_FORCED_VARIATION: '%s: User %s is not in the forced variation map.', @@ -172,7 +177,7 @@ export const CONTROL_ATTRIBUTES = { BUCKETING_ID: '$opt_bucketing_id', STICKY_BUCKETING_KEY: '$opt_experiment_bucket_map', USER_AGENT: '$opt_user_agent', - FORCED_DECISION_NULL_RULE_KEY: '$opt_null_rule_key' + FORCED_DECISION_NULL_RULE_KEY: '$opt_null_rule_key', }; export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; @@ -233,57 +238,57 @@ export const DATAFILE_VERSIONS = { */ export const enum VERSION_TYPE { PRE_RELEASE_VERSION_DELIMITER = '-', - BUILD_VERSION_DELIMITER = '+' + BUILD_VERSION_DELIMITER = '+', } export const DECISION_MESSAGES = { SDK_NOT_READY: 'Optimizely SDK not configured properly yet.', FLAG_KEY_INVALID: 'No flag was found for key "%s".', VARIABLE_VALUE_INVALID: 'Variable value for key "%s" is invalid or wrong type.', -} +}; /* -* Notification types for use with NotificationCenter -* Format is EVENT: -* -* SDK consumers can use these to register callbacks with the notification center. -* -* @deprecated since 3.1.0 -* ACTIVATE: An impression event will be sent to Optimizely -* Callbacks will receive an object argument with the following properties: -* - experiment {Object} -* - userId {string} -* - attributes {Object|undefined} -* - variation {Object} -* - logEvent {Object} -* -* DECISION: A decision is made in the system. i.e. user activation, -* feature access or feature-variable value retrieval -* Callbacks will receive an object argument with the following properties: -* - type {string} -* - userId {string} -* - attributes {Object|undefined} -* - decisionInfo {Object|undefined} -* -* LOG_EVENT: A batch of events, which could contain impressions and/or conversions, -* will be sent to Optimizely -* Callbacks will receive an object argument with the following properties: -* - url {string} -* - httpVerb {string} -* - params {Object} -* -* OPTIMIZELY_CONFIG_UPDATE: This Optimizely instance has been updated with a new -* config -* -* TRACK: A conversion event will be sent to Optimizely -* Callbacks will receive the an object argument with the following properties: -* - eventKey {string} -* - userId {string} -* - attributes {Object|undefined} -* - eventTags {Object|undefined} -* - logEvent {Object} -* -*/ + * Notification types for use with NotificationCenter + * Format is EVENT: + * + * SDK consumers can use these to register callbacks with the notification center. + * + * @deprecated since 3.1.0 + * ACTIVATE: An impression event will be sent to Optimizely + * Callbacks will receive an object argument with the following properties: + * - experiment {Object} + * - userId {string} + * - attributes {Object|undefined} + * - variation {Object} + * - logEvent {Object} + * + * DECISION: A decision is made in the system. i.e. user activation, + * feature access or feature-variable value retrieval + * Callbacks will receive an object argument with the following properties: + * - type {string} + * - userId {string} + * - attributes {Object|undefined} + * - decisionInfo {Object|undefined} + * + * LOG_EVENT: A batch of events, which could contain impressions and/or conversions, + * will be sent to Optimizely + * Callbacks will receive an object argument with the following properties: + * - url {string} + * - httpVerb {string} + * - params {Object} + * + * OPTIMIZELY_CONFIG_UPDATE: This Optimizely instance has been updated with a new + * config + * + * TRACK: A conversion event will be sent to Optimizely + * Callbacks will receive the an object argument with the following properties: + * - eventKey {string} + * - userId {string} + * - attributes {Object|undefined} + * - eventTags {Object|undefined} + * - logEvent {Object} + * + */ export enum NOTIFICATION_TYPES { ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', DECISION = 'DECISION:type, userId, attributes, decisionInfo', diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/index.ts b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts similarity index 74% rename from packages/optimizely-sdk/lib/core/odp/lru_cache/index.ts rename to packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts index cb21e5693..23daedf01 100644 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/index.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { LRUCache, ClientLRUCache, ServerLRUCache } from "./LRUCache"; +import LRUCache from './lru_cache'; -export { - LRUCache, - ClientLRUCache, - ServerLRUCache, -} \ No newline at end of file +export class BrowserLRUCache extends LRUCache { + constructor() { + super({ + maxSize: 100, + timeout: 600 * 1000, // 600 secs + }); + } +} diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/cache_element.tests.ts b/packages/optimizely-sdk/lib/utils/lru_cache/cache_element.tests.ts new file mode 100644 index 000000000..dfba16fa7 --- /dev/null +++ b/packages/optimizely-sdk/lib/utils/lru_cache/cache_element.tests.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { assert } from 'chai'; +import { CacheElement } from './cache_element'; + +const sleep = async (ms: number) => { + return await new Promise(r => setTimeout(r, ms)); +}; + +describe('/odp/lru_cache/CacheElement', () => { + let element: CacheElement; + + beforeEach(() => { + element = new CacheElement('foo'); + }); + + it('should initialize a valid CacheElement', () => { + assert.exists(element); + assert.equal(element.value, 'foo'); + assert.isNotNull(element.time); + assert.doesNotThrow(() => element.is_stale(0)); + }); + + it('should return false if not stale based on timeout', () => { + const timeoutLong = 1000; + assert.equal(element.is_stale(timeoutLong), false); + }); + + it('should return false if not stale because timeout is less than or equal to 0', () => { + const timeoutNone = 0; + assert.equal(element.is_stale(timeoutNone), false); + }); + + it('should return true if stale based on timeout', async () => { + await sleep(100); + const timeoutShort = 1; + assert.equal(element.is_stale(timeoutShort), true); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.ts b/packages/optimizely-sdk/lib/utils/lru_cache/cache_element.ts similarity index 62% rename from packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.ts rename to packages/optimizely-sdk/lib/utils/lru_cache/cache_element.ts index a8734527f..c286aab7a 100644 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/cache_element.ts @@ -18,21 +18,25 @@ * CacheElement represents an individual generic item within the LRUCache */ export class CacheElement { - private _value: V | null - private _time: number + private _value: V | null; + private _time: number; - get value(): V | null { return this._value } - get time(): number { return this._time } + get value(): V | null { + return this._value; + } + get time(): number { + return this._time; + } - constructor(value: V | null = null) { - this._value = value - this._time = Date.now() - } + constructor(value: V | null = null) { + this._value = value; + this._time = Date.now(); + } - public is_stale(timeout: number): boolean { - if (timeout <= 0) return false - return Date.now() - this._time >= timeout - } + public is_stale(timeout: number): boolean { + if (timeout <= 0) return false; + return Date.now() - this._time >= timeout; + } } -export default CacheElement \ No newline at end of file +export default CacheElement; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/index.ts b/packages/optimizely-sdk/lib/utils/lru_cache/index.ts new file mode 100644 index 000000000..185093ead --- /dev/null +++ b/packages/optimizely-sdk/lib/utils/lru_cache/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LRUCache } from './lru_cache'; +import { BrowserLRUCache } from './browser_lru_cache'; +import { ServerLRUCache } from './server_lru_cache'; + +export { LRUCache, BrowserLRUCache, ServerLRUCache }; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.tests.ts b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.tests.ts new file mode 100644 index 000000000..4c9de8d1a --- /dev/null +++ b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.tests.ts @@ -0,0 +1,309 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { assert } from 'chai'; +import { LRUCache } from './lru_cache'; +import { BrowserLRUCache } from './browser_lru_cache'; +import { ServerLRUCache } from './server_lru_cache'; + +const sleep = async (ms: number) => { + return await new Promise(r => setTimeout(r, ms)); +}; + +describe('/lib/core/odp/lru_cache (Default)', () => { + let cache: LRUCache; + + describe('LRU Cache > Initialization', () => { + it('should successfully create a new cache with maxSize > 0 and timeout > 0', () => { + cache = new LRUCache({ + maxSize: 1000, + timeout: 2000, + }); + + assert.exists(cache); + + assert.equal(cache.maxSize, 1000); + assert.equal(cache.timeout, 2000); + }); + + it('should successfully create a new cache with maxSize == 0 and timeout == 0', () => { + cache = new LRUCache({ + maxSize: 0, + timeout: 0, + }); + + assert.exists(cache); + + assert.equal(cache.maxSize, 0); + assert.equal(cache.timeout, 0); + }); + }); + + describe('LRU Cache > Save & Lookup', () => { + const maxCacheSize = 2; + + beforeEach(() => { + cache = new LRUCache({ + maxSize: maxCacheSize, + timeout: 1000, + }); + }); + + it('should have no values in the cache upon initialization', () => { + assert.isNull(cache.peek(1)); + }); + + it('should save keys and values of any valid type', () => { + cache.save({ key: 'a', value: 1 }); // { a: 1 } + assert.equal(cache.peek('a'), 1); + + cache.save({ key: 2, value: 'b' }); // { a: 1, 2: 'b' } + assert.equal(cache.peek(2), 'b'); + + const foo = Symbol('foo'); + const bar = {}; + cache.save({ key: foo, value: bar }); // { 2: 'b', Symbol('foo'): {} } + assert.deepEqual({}, cache.peek(foo)); + }); + + it('should save values up to its maxSize', () => { + cache.save({ key: 'a', value: 1 }); // { a: 1 } + assert.equal(cache.peek('a'), 1); + + cache.save({ key: 'b', value: 2 }); // { a: 1, b: 2 } + assert.equal(cache.peek('a'), 1); + assert.equal(cache.peek('b'), 2); + + cache.save({ key: 'c', value: 3 }); // { b: 2, c: 3 } + assert.equal(cache.peek('a'), null); + assert.equal(cache.peek('b'), 2); + assert.equal(cache.peek('c'), 3); + }); + + it('should override values of matching keys when saving', () => { + cache.save({ key: 'a', value: 1 }); // { a: 1 } + assert.equal(cache.peek('a'), 1); + + cache.save({ key: 'a', value: 2 }); // { a: 2 } + assert.equal(cache.peek('a'), 2); + + cache.save({ key: 'a', value: 3 }); // { a: 3 } + assert.equal(cache.peek('a'), 3); + }); + + it('should update cache accordingly when using lookup/peek', () => { + assert.isNull(cache.lookup(3)); + + cache.save({ key: 'b', value: 201 }); // { b: 201 } + cache.save({ key: 'a', value: 101 }); // { b: 201, a: 101 } + + assert.equal(cache.lookup('b'), 201); // { a: 101, b: 201 } + + cache.save({ key: 'c', value: 302 }); // { b: 201, c: 302 } + + assert.isNull(cache.peek(1)); + assert.equal(cache.peek('b'), 201); + assert.equal(cache.peek('c'), 302); + assert.equal(cache.lookup('c'), 302); // { b: 201, c: 302 } + + cache.save({ key: 'a', value: 103 }); // { c: 302, a: 103 } + assert.equal(cache.peek('a'), 103); + assert.isNull(cache.peek('b')); + assert.equal(cache.peek('c'), 302); + }); + }); + + describe('LRU Cache > Size', () => { + it('should keep LRU Cache map size capped at cache.capacity', () => { + const maxCacheSize = 2; + + cache = new LRUCache({ + maxSize: maxCacheSize, + timeout: 1000, + }); + + cache.save({ key: 'a', value: 1 }); // { a: 1 } + cache.save({ key: 'b', value: 2 }); // { a: 1, b: 2 } + + assert.equal(cache.map.size, maxCacheSize); + assert.equal(cache.map.size, cache.maxSize); + }); + + it('should not save to cache if maxSize is 0', () => { + cache = new LRUCache({ + maxSize: 0, + timeout: 1000, + }); + + assert.isNull(cache.lookup('a')); + cache.save({ key: 'a', value: 100 }); + assert.isNull(cache.lookup('a')); + }); + + it('should not save to cache if maxSize is negative', () => { + cache = new LRUCache({ + maxSize: -500, + timeout: 1000, + }); + + assert.isNull(cache.lookup('a')); + cache.save({ key: 'a', value: 100 }); + assert.isNull(cache.lookup('a')); + }); + }); + + describe('LRU Cache > Timeout', () => { + it('should discard stale entries in the cache on peek/lookup when timeout is greater than 0', async () => { + const maxTimeout = 100; + + cache = new LRUCache({ + maxSize: 1000, + timeout: maxTimeout, + }); + + cache.save({ key: 'a', value: 100 }); // { a: 100 } + cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } + cache.save({ key: 'c', value: 300 }); // { a: 100, b: 200, c: 300 } + + assert.equal(cache.peek('a'), 100); + assert.equal(cache.peek('b'), 200); + assert.equal(cache.peek('c'), 300); + + await sleep(150); + + assert.isNull(cache.lookup('a')); + assert.isNull(cache.lookup('b')); + assert.isNull(cache.lookup('c')); + + cache.save({ key: 'd', value: 400 }); // { d: 400 } + cache.save({ key: 'a', value: 101 }); // { d: 400, a: 101 } + + assert.equal(cache.lookup('a'), 101); // { d: 400, a: 101 } + assert.equal(cache.lookup('d'), 400); // { a: 101, d: 400 } + }); + + it('should never have stale entries if timeout is 0', async () => { + const maxTimeout = 0; + + cache = new LRUCache({ + maxSize: 1000, + timeout: maxTimeout, + }); + + cache.save({ key: 'a', value: 100 }); // { a: 100 } + cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } + + await sleep(100); + assert.equal(cache.lookup('a'), 100); + assert.equal(cache.lookup('b'), 200); + }); + + it('should never have stale entries if timeout is less than 0', async () => { + const maxTimeout = -500; + + cache = new LRUCache({ + maxSize: 1000, + timeout: maxTimeout, + }); + + cache.save({ key: 'a', value: 100 }); // { a: 100 } + cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } + + await sleep(100); + assert.equal(cache.lookup('a'), 100); + assert.equal(cache.lookup('b'), 200); + }); + }); + + describe('LRU Cache > Reset', () => { + it('should be able to reset the cache', async () => { + cache = new LRUCache({ maxSize: 2, timeout: 100 }); + cache.save({ key: 'a', value: 100 }); // { a: 100 } + cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } + + await sleep(0); + + assert.equal(cache.map.size, 2); + cache.reset(); // { } + + await sleep(150); + + assert.equal(cache.map.size, 0); + + it('should be fully functional after resetting the cache', () => { + cache.save({ key: 'c', value: 300 }); // { c: 300 } + cache.save({ key: 'd', value: 400 }); // { c: 300, d: 400 } + assert.isNull(cache.peek('b')); + assert.equal(cache.peek('c'), 300); + assert.equal(cache.peek('d'), 400); + + cache.save({ key: 'a', value: 500 }); // { d: 400, a: 500 } + cache.save({ key: 'b', value: 600 }); // { a: 500, b: 600 } + assert.isNull(cache.peek('c')); + assert.equal(cache.peek('a'), 500); + assert.equal(cache.peek('b'), 600); + + const _ = cache.lookup('a'); // { b: 600, a: 500 } + assert.equal(500, _); + + cache.save({ key: 'c', value: 700 }); // { a: 500, c: 700 } + assert.isNull(cache.peek('b')); + assert.equal(cache.peek('a'), 500); + assert.equal(cache.peek('c'), 700); + }); + }); + }); +}); + +describe('/lib/core/odp/lru_cache (Client)', () => { + let cache: BrowserLRUCache; + + it('should create and test the default client LRU Cache', () => { + cache = new BrowserLRUCache(); + assert.exists(cache); + assert.isNull(cache.lookup('a')); + assert.equal(cache.maxSize, 100); + assert.equal(cache.timeout, 600 * 1000); + + cache.save({ key: 'a', value: 100 }); + cache.save({ key: 'b', value: 200 }); + cache.save({ key: 'c', value: 300 }); + assert.equal(cache.map.size, 3); + assert.equal(cache.peek('a'), 100); + assert.equal(cache.lookup('b'), 200); + assert.deepEqual(cache.map.keys().next().value, 'a'); + }); +}); + +describe('/lib/core/odp/lru_cache (Server)', () => { + let cache: ServerLRUCache; + + it('should create and test the default server LRU Cache', () => { + cache = new ServerLRUCache(); + assert.exists(cache); + assert.isNull(cache.lookup('a')); + assert.equal(cache.maxSize, 10000); + assert.equal(cache.timeout, 600 * 1000); + + cache.save({ key: 'a', value: 100 }); + cache.save({ key: 'b', value: 200 }); + cache.save({ key: 'c', value: 300 }); + assert.equal(cache.map.size, 3); + assert.equal(cache.peek('a'), 100); + assert.equal(cache.lookup('b'), 200); + assert.deepEqual(cache.map.keys().next().value, 'a'); + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts new file mode 100644 index 000000000..b0b2a60f5 --- /dev/null +++ b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import CacheElement from './cache_element'; + +/** + * Least-Recently Used Cache (LRU Cache) Implementation with Generic Key-Value Pairs + * Analogous to a Map that has a specified max size and a timeout per element. + * - Removes the least-recently used element from the cache if max size exceeded. + * - Removes stale elements (entries older than their timeout) from the cache. + */ +export class LRUCache { + private _map: Map> = new Map(); + private _maxSize; // Defines maximum size of _map + private _timeout; // Milliseconds each entry has before it becomes stale + + get map(): Map> { + return this._map; + } + get maxSize(): number { + return this._maxSize; + } + get timeout(): number { + return this._timeout; + } + + constructor({ maxSize, timeout }: { maxSize: number; timeout: number }) { + this._maxSize = maxSize; + this._timeout = timeout; + } + + /** + * Returns a valid, non-stale value from LRU Cache based on an input key. + * Additionally moves the element to the end of the cache and removes from cache if stale. + */ + public lookup(key: K): V | null { + if (this._maxSize <= 0) { + return null; + } + + const element: CacheElement | undefined = this._map.get(key); + + if (!element) return null; + + if (element.is_stale(this._timeout)) { + this._map.delete(key); + return null; + } + + this._map.delete(key); + this._map.set(key, element); + + return element.value; + } + + /** + * Inserts/moves an input key-value pair to the end of the LRU Cache. + * Removes the least-recently used element if the cache exceeds it's maxSize. + */ + public save({ key, value }: { key: K; value: V }): void { + if (this._maxSize <= 0) return; + + const element: CacheElement | undefined = this._map.get(key); + if (element) this._map.delete(key); + this._map.set(key, new CacheElement(value)); + + if (this._map.size > this._maxSize) { + const firstMapEntryKey = this._map.keys().next().value; + this._map.delete(firstMapEntryKey); + } + } + + /** + * Clears the LRU Cache + */ + public reset(): void { + if (this._maxSize <= 0) return; + + this._map.clear(); + } + + /** + * Reads value from specified key without moving elements in the LRU Cache. + * @param {K} key + */ + public peek(key: K): V | null { + if (this._maxSize <= 0) return null; + + const element: CacheElement | undefined = this._map.get(key); + + return element?.value ?? null; + } +} + +export default LRUCache; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts new file mode 100644 index 000000000..f39e15894 --- /dev/null +++ b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import LRUCache from './lru_cache'; + +export class ServerLRUCache extends LRUCache { + constructor() { + super({ + maxSize: 10000, + timeout: 600 * 1000, // 600 secs + }); + } +} diff --git a/packages/optimizely-sdk/tests/restApiManager.spec.ts b/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts similarity index 83% rename from packages/optimizely-sdk/tests/restApiManager.spec.ts rename to packages/optimizely-sdk/tests/odpEventApiManager.spec.ts index 132649da7..49492a8c6 100644 --- a/packages/optimizely-sdk/tests/restApiManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts @@ -18,8 +18,8 @@ import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { RestApiManager } from '../lib/plugins/odp/rest_api_manager'; -import { OdpEvent } from '../lib/plugins/odp/odp_event'; +import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; +import { OdpEvent } from '../lib/core/odp/odp_event'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; const VALID_ODP_PUBLIC_KEY = 'not-real-api-key'; @@ -32,15 +32,11 @@ data1.set('key14', null); const data2 = new Map(); data2.set('key2', 'value-2'); const ODP_EVENTS = [ - new OdpEvent('t1', 'a1', - new Map([['id-key-1', 'id-value-1']]), - data1), - new OdpEvent('t2', 'a2', - new Map([['id-key-2', 'id-value-2']]), - data2), + new OdpEvent('t1', 'a1', new Map([['id-key-1', 'id-value-1']]), data1), + new OdpEvent('t2', 'a2', new Map([['id-key-2', 'id-value-2']]), data2), ]; -describe('RestApiManager', () => { +describe('OdpEventApiManager', () => { let mockLogger: LogHandler; let mockRequestHandler: RequestHandler; @@ -54,11 +50,10 @@ describe('RestApiManager', () => { resetCalls(mockRequestHandler); }); - const managerInstance = () => new RestApiManager(instance(mockRequestHandler), instance(mockLogger)); + const managerInstance = () => new OdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); const abortableRequest = (statusCode: number, body: string) => { return { - abort: () => { - }, + abort: () => {}, responsePromise: Promise.resolve({ statusCode, body, @@ -68,7 +63,9 @@ describe('RestApiManager', () => { }; it('should should send events successfully and not suggest retry', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, '')); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, '') + ); const manager = managerInstance(); const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); @@ -78,7 +75,9 @@ describe('RestApiManager', () => { }); it('should not suggest a retry for 400 HTTP response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(400, '')); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(400, '') + ); const manager = managerInstance(); const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); @@ -88,7 +87,9 @@ describe('RestApiManager', () => { }); it('should suggest a retry for 500 HTTP response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(500, '')); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(500, '') + ); const manager = managerInstance(); const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); @@ -99,8 +100,7 @@ describe('RestApiManager', () => { it('should suggest a retry for network timeout', async () => { when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ - abort: () => { - }, + abort: () => {}, responsePromise: Promise.reject(new Error('Request timed out')), }); const manager = managerInstance(); diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index 12ee14a84..13f70d1e9 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { OdpConfig } from '../lib/plugins/odp/odp_config'; -import { OdpEventManager, STATE } from '../lib/plugins/odp/odp_event_manager'; +import { OdpConfig } from '../lib/core/odp/odp_config'; +import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; -import { RestApiManager } from '../lib/plugins/odp/rest_api_manager'; +import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { OdpEvent } from '../lib/plugins/odp/odp_event'; +import { OdpEvent } from '../lib/core/odp/odp_event'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; const API_KEY = 'test-api-key'; @@ -30,21 +30,25 @@ const EVENTS: OdpEvent[] = [ 't1', 'a1', new Map([['id-key-1', 'id-value-1']]), - new Map(Object.entries({ - 'key-1': 'value1', - 'key-2': null, - 'key-3': 3.3, - 'key-4': true, - })), + new Map( + Object.entries({ + 'key-1': 'value1', + 'key-2': null, + 'key-3': 3.3, + 'key-4': true, + }) + ) ), new OdpEvent( 't2', 'a2', new Map([['id-key-2', 'id-value-2']]), - new Map(Object.entries({ - 'key-2': 'value2', - 'data_source': 'my-source', - })), + new Map( + Object.entries({ + 'key-2': 'value2', + data_source: 'my-source', + }) + ) ), ]; // naming for object destructuring @@ -55,28 +59,32 @@ const PROCESSED_EVENTS: OdpEvent[] = [ 't1', 'a1', new Map([['id-key-1', 'id-value-1']]), - new Map(Object.entries({ - 'idempotence_id': MOCK_IDEMPOTENCE_ID, - 'data_source_type': 'sdk', - 'data_source': clientEngine, - 'data_source_version': clientVersion, - 'key-1': 'value1', - 'key-2': null, - 'key-3': 3.3, - 'key-4': true, - })), + new Map( + Object.entries({ + idempotence_id: MOCK_IDEMPOTENCE_ID, + data_source_type: 'sdk', + data_source: clientEngine, + data_source_version: clientVersion, + 'key-1': 'value1', + 'key-2': null, + 'key-3': 3.3, + 'key-4': true, + }) + ) ), new OdpEvent( 't2', 'a2', new Map([['id-key-2', 'id-value-2']]), - new Map(Object.entries({ - 'idempotence_id': MOCK_IDEMPOTENCE_ID, - 'data_source_type': 'sdk', - 'data_source': clientEngine, - 'data_source_version': clientVersion, - 'key-2': 'value2', - })), + new Map( + Object.entries({ + idempotence_id: MOCK_IDEMPOTENCE_ID, + data_source_type: 'sdk', + data_source: clientEngine, + data_source_version: clientVersion, + 'key-2': 'value2', + }) + ) ), ]; const makeEvent = (id: number) => { @@ -95,8 +103,7 @@ const pause = (timeoutMilliseconds: number): Promise => { }; const abortableRequest = (statusCode: number, body: string) => { return { - abort: () => { - }, + abort: () => {}, responsePromise: Promise.resolve({ statusCode, body, @@ -107,15 +114,15 @@ const abortableRequest = (statusCode: number, body: string) => { describe('OdpEventManager', () => { let mockLogger: LogHandler; - let mockApiManager: RestApiManager; + let mockApiManager: OdpEventApiManager; let odpConfig: OdpConfig; let logger: LogHandler; - let apiManager: RestApiManager; + let apiManager: OdpEventApiManager; beforeAll(() => { mockLogger = mock(); - mockApiManager = mock(); + mockApiManager = mock(); odpConfig = new OdpConfig(API_KEY, API_HOST, []); logger = instance(mockLogger); @@ -129,7 +136,11 @@ describe('OdpEventManager', () => { it('should log and discard events when event manager not running', () => { const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, }); // since we've not called start() then... @@ -144,7 +155,11 @@ describe('OdpEventManager', () => { when(mockOdpConfig.isReady()).thenReturn(false); const odpConfig = instance(mockOdpConfig); const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, }); eventManager['state'] = STATE.RUNNING; // simulate running without calling start() @@ -155,17 +170,23 @@ describe('OdpEventManager', () => { it('should discard events with invalid data', () => { const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, }); // make an event with invalid data key-value entry const badEvent = new OdpEvent( 't3', 'a3', new Map([['id-key-3', 'id-value-3']]), - new Map(Object.entries({ - 'key-1': false, - 'key-2': { random: 'object', whichShouldFail: true }, - })), + new Map( + Object.entries({ + 'key-1': false, + 'key-2': { random: 'object', whichShouldFail: true }, + }) + ) ); eventManager.sendEvent(badEvent); @@ -175,7 +196,12 @@ describe('OdpEventManager', () => { it('should log a max queue hit and discard ', () => { // set queue to maximum of 1 const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, queueSize: 1, // With max queue size set to 1... + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + queueSize: 1, // With max queue size set to 1... }); eventManager['state'] = STATE.RUNNING; eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... @@ -183,18 +209,26 @@ describe('OdpEventManager', () => { // ...try adding the second event eventManager.sendEvent(EVENTS[1]); - verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', 1)).once(); + verify( + mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', 1) + ).once(); }); it('should add additional information to each event', () => { const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, }); const processedEventData = PROCESSED_EVENTS[0].data; const eventData = eventManager['augmentCommonData'](EVENTS[0].data); - expect((eventData.get('idempotence_id') as string).length).toEqual((processedEventData.get('idempotence_id') as string).length); + expect((eventData.get('idempotence_id') as string).length).toEqual( + (processedEventData.get('idempotence_id') as string).length + ); expect(eventData.get('data_source_type')).toEqual(processedEventData.get('data_source_type')); expect(eventData.get('data_source')).toEqual(processedEventData.get('data_source')); expect(eventData.get('data_source_version')).toEqual(processedEventData.get('data_source_version')); @@ -206,7 +240,11 @@ describe('OdpEventManager', () => { it('should attempt to flush an empty queue at flush intervals', async () => { const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, flushInterval: 100, }); const spiedEventManager = spy(eventManager); @@ -222,7 +260,11 @@ describe('OdpEventManager', () => { when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, batchSize: 10, // with batch size of 10... flushInterval: 250, }); @@ -240,7 +282,13 @@ describe('OdpEventManager', () => { it('should dispatch events with correct payload', async () => { const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, batchSize: 10, flushInterval: 100, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 10, + flushInterval: 100, }); eventManager.start(); @@ -264,8 +312,12 @@ describe('OdpEventManager', () => { when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(true); const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - batchSize: 2, // batch size of 2 + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 2, // batch size of 2 flushInterval: 100, }); @@ -284,8 +336,12 @@ describe('OdpEventManager', () => { when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - batchSize: 2, // batches of 2 with... + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 2, // batches of 2 with... flushInterval: 100, }); @@ -303,10 +359,18 @@ describe('OdpEventManager', () => { it('should prepare correct payload for register VUID', async () => { const mockRequestHandler: RequestHandler = mock(); - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, '')); - const apiManager = new RestApiManager(instance(mockRequestHandler), logger); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, '') + ); + const apiManager = new OdpEventApiManager(instance(mockRequestHandler), logger); const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, batchSize: 10, flushInterval: 100, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 10, + flushInterval: 100, }); const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; @@ -323,7 +387,7 @@ describe('OdpEventManager', () => { const event = events[0]; expect(event.type).toEqual('fullstack'); expect(event.action).toEqual('client_initialized'); - expect(event.identifiers).toEqual({ 'vuid': vuid }); + expect(event.identifiers).toEqual({ vuid: vuid }); expect(event.data.idempotence_id.length).toBe(36); // uuid length expect(event.data.data_source_type).toEqual('sdk'); expect(event.data.data_source).toEqual('javascript-sdk'); @@ -332,10 +396,17 @@ describe('OdpEventManager', () => { it('should prepare correct payload for identify user', async () => { const mockRequestHandler: RequestHandler = mock(); - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, '')); - const apiManager = new RestApiManager(instance(mockRequestHandler), logger); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, '') + ); + const apiManager = new OdpEventApiManager(instance(mockRequestHandler), logger); const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, flushInterval: 100, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + flushInterval: 100, }); const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; const fsUserId = 'test-fs-user-id'; @@ -353,7 +424,7 @@ describe('OdpEventManager', () => { const event = events[0]; expect(event.type).toEqual('fullstack'); expect(event.action).toEqual('identified'); - expect(event.identifiers).toEqual({ 'vuid': vuid, 'fs_user_id': fsUserId }); + expect(event.identifiers).toEqual({ vuid: vuid, fs_user_id: fsUserId }); expect(event.data.idempotence_id.length).toBe(36); // uuid length expect(event.data.data_source_type).toEqual('sdk'); expect(event.data.data_source).toEqual('javascript-sdk'); @@ -362,7 +433,11 @@ describe('OdpEventManager', () => { it('should apply updated ODP configuration when available', () => { const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, }); const apiKey = 'testing-api-key'; const apiHost = 'https://some.other.example.com/'; diff --git a/packages/optimizely-sdk/tests/graphQlManager.spec.ts b/packages/optimizely-sdk/tests/odpSegmentApiManager.ts similarity index 83% rename from packages/optimizely-sdk/tests/graphQlManager.spec.ts rename to packages/optimizely-sdk/tests/odpSegmentApiManager.ts index 8f2c228ff..5056145a2 100644 --- a/packages/optimizely-sdk/tests/graphQlManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpSegmentApiManager.ts @@ -18,7 +18,7 @@ import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { GraphQLManager } from '../lib/plugins/odp/graphql_manager'; +import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { ODP_USER_KEY } from '../lib/utils/enums'; @@ -26,13 +26,9 @@ const API_key = 'not-real-api-key'; const GRAPHQL_ENDPOINT = 'https://some.example.com/graphql/endpoint'; const USER_KEY = ODP_USER_KEY.FS_USER_ID; const USER_VALUE = 'tester-101'; -const SEGMENTS_TO_CHECK = [ - 'has_email', - 'has_email_opted_in', - 'push_on_sale', -]; +const SEGMENTS_TO_CHECK = ['has_email', 'has_email_opted_in', 'push_on_sale']; -describe('GraphQLManager', () => { +describe('OdpSegmentApiManager', () => { let mockLogger: LogHandler; let mockRequestHandler: RequestHandler; @@ -46,12 +42,11 @@ describe('GraphQLManager', () => { resetCalls(mockRequestHandler); }); - const managerInstance = () => new GraphQLManager(instance(mockRequestHandler), instance(mockLogger)); + const managerInstance = () => new OdpSegmentApiManager(instance(mockRequestHandler), instance(mockLogger)); const abortableRequest = (statusCode: number, body: string) => { return { - abort: () => { - }, + abort: () => {}, responsePromise: Promise.resolve({ statusCode, body, @@ -137,17 +132,20 @@ describe('GraphQLManager', () => { const response = manager['toGraphQLJson'](USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - expect(response) - .toBe(`{"query" : "query {customer"(${USER_KEY} : "${USER_VALUE}") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"] {edges {node {name state}}}}}"}`, - ); + expect(response).toBe( + `{"query" : "query {customer"(${USER_KEY} : "${USER_VALUE}") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"] {edges {node {name state}}}}}"}` + ); }); it('should fetch valid qualified segments', async () => { - const responseJsonWithQualifiedSegments = '{"data":{"customer":{"audiences":' + + const responseJsonWithQualifiedSegments = + '{"data":{"customer":{"audiences":' + '{"edges":[{"node":{"name":"has_email",' + '"state":"qualified"}},{"node":{"name":' + '"has_email_opted_in","state":"qualified"}}]}}}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, responseJsonWithQualifiedSegments)); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, responseJsonWithQualifiedSegments) + ); const manager = managerInstance(); const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); @@ -168,9 +166,10 @@ describe('GraphQLManager', () => { }); it('should handle empty qualified segments', async () => { - const responseJsonWithNoQualifiedSegments = '{"data":{"customer":{"audiences":' + - '{"edges":[ ]}}}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, responseJsonWithNoQualifiedSegments)); + const responseJsonWithNoQualifiedSegments = '{"data":{"customer":{"audiences":' + '{"edges":[ ]}}}}'; + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, responseJsonWithNoQualifiedSegments) + ); const manager = managerInstance(); const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); @@ -181,16 +180,25 @@ describe('GraphQLManager', () => { it('should handle error with invalid identifier', async () => { const INVALID_USER_ID = 'invalid-user'; - const errorJsonResponse = '{"errors":[{"message":' + + const errorJsonResponse = + '{"errors":[{"message":' + '"Exception while fetching data (/customer) : ' + `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + '"locations":[{"line":1,"column":8}],"path":["customer"],' + '"extensions":{"classification":"DataFetchingException"}}],' + '"data":{"customer":null}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, errorJsonResponse)); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, errorJsonResponse) + ); const manager = managerInstance(); - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, INVALID_USER_ID, SEGMENTS_TO_CHECK); + const segments = await manager.fetchSegments( + API_key, + GRAPHQL_ENDPOINT, + USER_KEY, + INVALID_USER_ID, + SEGMENTS_TO_CHECK + ); expect(segments).toBeNull(); verify(mockLogger.log(anything(), anyString())).once(); @@ -198,7 +206,9 @@ describe('GraphQLManager', () => { it('should handle unrecognized JSON responses', async () => { const unrecognizedJson = '{"unExpectedObject":{ "withSome": "value", "thatIsNotParseable": "true" }}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, unrecognizedJson)); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, unrecognizedJson) + ); const manager = managerInstance(); const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); @@ -208,11 +218,14 @@ describe('GraphQLManager', () => { }); it('should handle other exception types', async () => { - const errorJsonResponse = '{"errors":[{"message":"Validation error of type ' + + const errorJsonResponse = + '{"errors":[{"message":"Validation error of type ' + 'UnknownArgument: Unknown field argument not_real_userKey @ ' + '\'customer\'","locations":[{"line":1,"column":17}],' + '"extensions":{"classification":"ValidationError"}}]}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, errorJsonResponse)); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, errorJsonResponse) + ); const manager = managerInstance(); const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); @@ -223,7 +236,9 @@ describe('GraphQLManager', () => { it('should handle bad responses', async () => { const badResponse = '{"data":{ }}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, badResponse)); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, badResponse) + ); const manager = managerInstance(); const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); @@ -233,7 +248,9 @@ describe('GraphQLManager', () => { }); it('should handle non 200 HTTP status code response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(400, '')); + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(400, '') + ); const manager = managerInstance(); const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); @@ -244,8 +261,7 @@ describe('GraphQLManager', () => { it('should handle a timeout', async () => { when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ - abort: () => { - }, + abort: () => {}, responsePromise: Promise.reject(new Error('Request timed out')), }); const manager = managerInstance(); diff --git a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts new file mode 100644 index 000000000..91277c5ee --- /dev/null +++ b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts @@ -0,0 +1,138 @@ +/** + * Copyright 2022, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// + +import { mock, resetCalls, instance } from 'ts-mockito'; + +import { LogHandler } from '../lib/modules/logging'; +import { ODP_USER_KEY } from '../lib/utils/enums'; +import { RequestHandler } from '../lib/utils/http_request_handler/http'; + +import { OdpSegmentManager } from '../lib/core/odp/odp_segment_manager'; +import { OdpConfig } from '../lib/core/odp/odp_config'; +import { LRUCache } from '../lib/utils/lru_cache'; +import { OptimizelySegmentOption } from './../lib/core/odp/optimizely_segment_option'; +import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; + +describe('OdpSegmentManager', () => { + class MockOdpSegmentApiManager extends OdpSegmentApiManager { + public async fetchSegments( + apiKey: string, + apiHost: string, + userKey: ODP_USER_KEY, + userValue: string, + segmentsToCheck: string[] + ): Promise { + if (apiKey == 'invalid-key') return null; + return segmentsToCheck; + } + } + + const mockLogHandler = mock(); + const mockRequestHandler = mock(); + + let manager: OdpSegmentManager; + let odpConfig: OdpConfig; + const apiManager = new MockOdpSegmentApiManager(instance(mockRequestHandler), instance(mockLogHandler)); + + let options: Array = []; + + const userKey: ODP_USER_KEY = ODP_USER_KEY.VUID; + const userValue = 'test-user'; + + beforeEach(() => { + resetCalls(mockLogHandler); + resetCalls(mockRequestHandler); + + const API_KEY = 'test-api-key'; + const API_HOST = 'https://odp.example.com/'; + odpConfig = new OdpConfig(API_KEY, API_HOST, []); + const segmentsCache = new LRUCache>({ + maxSize: 1000, + timeout: 1000, + }); + + manager = new OdpSegmentManager(odpConfig, segmentsCache, apiManager); + }); + + it('should fetch segments successfully on cache miss.', async () => { + odpConfig.update('host', 'valid', ['new-customer']); + setCache(userKey, '123', ['a']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); + expect(segments).toEqual(['new-customer']); + }); + + it('should fetch segments successfully on cache hit.', async () => { + odpConfig.update('host', 'valid', ['new-customer']); + setCache(userKey, userValue, ['a']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); + expect(segments).toEqual(['a']); + }); + + it('should throw an error when fetching segments returns an error.', async () => { + odpConfig.update('host', 'invalid-key', ['new-customer']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, []); + expect(segments).toBeNull; + }); + + it('should ignore the cache if the option is included in the options array.', async () => { + odpConfig.update('host', 'valid', ['new-customer']); + setCache(userKey, userValue, ['a']); + options = [OptimizelySegmentOption.IGNORE_CACHE]; + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); + expect(segments).toEqual(['new-customer']); + expect(cacheCount()).toBe(1); + }); + + it('should reset the cache if the option is included in the options array.', async () => { + odpConfig.update('host', 'valid', ['new-customer']); + setCache(userKey, userValue, ['a']); + setCache(userKey, '123', ['a']); + setCache(userKey, '456', ['a']); + options = [OptimizelySegmentOption.RESET_CACHE]; + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); + expect(segments).toEqual(['new-customer']); + expect(peekCache(userKey, userValue)).toEqual(segments); + expect(cacheCount()).toBe(1); + }); + + it('should make a valid cache key.', () => { + expect('vuid-$-test-user').toBe(manager.makeCacheKey(userKey, userValue)); + }); + + // Utility Functions + + function setCache(userKey: string, userValue: string, value: Array) { + const cacheKey = manager.makeCacheKey(userKey, userValue); + manager.segmentsCache.save({ + key: cacheKey, + value, + }); + } + + function peekCache(userKey: string, userValue: string): Array | null { + const cacheKey = manager.makeCacheKey(userKey, userValue); + return manager.segmentsCache.peek(cacheKey); + } + + const cacheCount = () => manager.segmentsCache.map.size; +}); From 0116d75c506cb58e0e44acd9be48f8ddbdde6db7 Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Fri, 24 Feb 2023 12:49:24 -0500 Subject: [PATCH 002/200] [FSSDK-8452] feat: Added ODP Manager Implementation (#797) --- .../notification_registry.tests.ts | 62 ++++ .../notification_registry.ts | 68 ++++ .../optimizely-sdk/lib/core/odp/odp_config.ts | 37 ++- .../lib/core/odp/odp_event_manager.ts | 63 ++-- .../lib/core/odp/odp_manager.ts | 246 +++++++++++++++ .../lib/core/odp/odp_segment_manager.ts | 61 +++- .../optimizely-sdk/lib/core/odp/odp_types.ts | 2 +- .../optimizely-sdk/lib/core/odp/odp_utils.ts | 32 ++ .../lib/core/project_config/index.tests.js | 10 +- .../lib/core/project_config/index.ts | 291 ++++++++---------- .../optimizely-sdk/lib/index.browser.tests.js | 2 +- .../httpPollingDatafileManager.ts | 11 +- .../optimizely-sdk/lib/optimizely/index.ts | 45 ++- .../no_op_datafile_manager.ts | 11 +- .../lib/plugins/odp_manager/index.browser.ts | 123 ++++++++ .../lib/plugins/odp_manager/index.node.ts | 59 ++++ .../lib/plugins/vuid_manager/index.ts | 25 +- packages/optimizely-sdk/lib/shared_types.ts | 116 +++---- .../optimizely-sdk/lib/utils/enums/index.ts | 41 ++- .../optimizely-sdk/lib/utils/fns/index.ts | 13 + .../lib/utils/lru_cache/browser_lru_cache.ts | 13 +- .../lib/utils/lru_cache/lru_cache.ts | 7 +- .../lib/utils/lru_cache/server_lru_cache.ts | 13 +- .../tests/odpEventManager.spec.ts | 12 +- .../tests/odpManager.browser.spec.ts | 219 +++++++++++++ .../optimizely-sdk/tests/odpManager.spec.ts | 207 +++++++++++++ ...anager.ts => odpSegmentApiManager.spec.ts} | 8 +- .../tests/odpSegmentManager.spec.ts | 21 +- .../optimizely-sdk/tests/vuidManager.spec.ts | 23 +- packages/optimizely-sdk/tsconfig.json | 1 + 30 files changed, 1467 insertions(+), 375 deletions(-) create mode 100644 packages/optimizely-sdk/lib/core/notification_center/notification_registry.tests.ts create mode 100644 packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts create mode 100644 packages/optimizely-sdk/lib/core/odp/odp_manager.ts create mode 100644 packages/optimizely-sdk/lib/core/odp/odp_utils.ts create mode 100644 packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts create mode 100644 packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts create mode 100644 packages/optimizely-sdk/tests/odpManager.browser.spec.ts create mode 100644 packages/optimizely-sdk/tests/odpManager.spec.ts rename packages/optimizely-sdk/tests/{odpSegmentApiManager.ts => odpSegmentApiManager.spec.ts} (98%) diff --git a/packages/optimizely-sdk/lib/core/notification_center/notification_registry.tests.ts b/packages/optimizely-sdk/lib/core/notification_center/notification_registry.tests.ts new file mode 100644 index 000000000..3a99b052c --- /dev/null +++ b/packages/optimizely-sdk/lib/core/notification_center/notification_registry.tests.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it } from 'mocha'; +import { expect } from 'chai'; + +import { NotificationRegistry } from './notification_registry'; + +describe('Notification Registry', () => { + it('Returns null notification center when SDK Key is null', () => { + const notificationCenter = NotificationRegistry.getNotificationCenter(); + expect(notificationCenter).to.be.undefined; + }); + + it('Returns the same notification center when SDK Keys are the same and not null', () => { + const sdkKey = 'testSDKKey'; + const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKey); + const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKey); + expect(notificationCenterA).to.eql(notificationCenterB); + }); + + it('Returns different notification centers when SDK Keys are not the same', () => { + const sdkKeyA = 'testSDKKeyA'; + const sdkKeyB = 'testSDKKeyB'; + const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKeyA); + const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKeyB); + expect(notificationCenterA).to.not.eql(notificationCenterB); + }); + + it('Removes old notification centers from the registry when removeNotificationCenter is called on the registry', () => { + const sdkKey = 'testSDKKey'; + const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKey); + NotificationRegistry.removeNotificationCenter(sdkKey); + + const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKey); + + expect(notificationCenterA).to.not.eql(notificationCenterB); + }); + + it('Does not throw an error when calling removeNotificationCenter with a null SDK Key', () => { + const sdkKey = 'testSDKKey'; + const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKey); + NotificationRegistry.removeNotificationCenter(); + + const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKey); + + expect(notificationCenterA).to.eql(notificationCenterB); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts b/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts new file mode 100644 index 000000000..80f9eb9f6 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts @@ -0,0 +1,68 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +import { NotificationCenter, createNotificationCenter } from '../../core/notification_center'; + +/** + * Internal notification center registry for managing multiple notification centers. + */ +export class NotificationRegistry { + private static _notificationCenters = new Map(); + + constructor() {} + + /** + * Retrieves an SDK Key's corresponding notification center in the registry if it exists, otherwise it creates one + * @param sdkKey SDK Key to be used for the notification center tied to the ODP Manager + * @param logger Logger to be used for the corresponding notification center + * @returns {NotificationCenter | undefined} a notification center instance for ODP Manager if a valid SDK Key is provided, otherwise undefined + */ + public static getNotificationCenter( + sdkKey?: string, + logger: LogHandler = getLogger() + ): NotificationCenter | undefined { + if (!sdkKey) { + logger.log(LogLevel.ERROR, 'No SDK key provided to getNotificationCenter.'); + return undefined; + } + + let notificationCenter; + if (this._notificationCenters.has(sdkKey)) { + notificationCenter = this._notificationCenters.get(sdkKey); + } else { + notificationCenter = createNotificationCenter({ + logger, + errorHandler: { handleError: () => {} }, + }); + this._notificationCenters.set(sdkKey, notificationCenter); + } + + return notificationCenter; + } + + public static removeNotificationCenter(sdkKey?: string): void { + if (!sdkKey) { + return; + } + + const notificationCenter = this._notificationCenters.get(sdkKey); + if (notificationCenter) { + notificationCenter.clearAllNotificationListeners(); + this._notificationCenters.delete(sdkKey); + } + } +} diff --git a/packages/optimizely-sdk/lib/core/odp/odp_config.ts b/packages/optimizely-sdk/lib/core/odp/odp_config.ts index 215d3655c..3bf407281 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_config.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_config.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,8 @@ * limitations under the License. */ +import { checkArrayEquality } from '../../../lib/utils/fns'; + export class OdpConfig { /** * Host of ODP audience segments API. @@ -57,26 +59,24 @@ export class OdpConfig { return this._segmentsToCheck; } - constructor(apiKey: string, apiHost: string, segmentsToCheck?: string[]) { - this._apiKey = apiKey; - this._apiHost = apiHost; + constructor(apiKey?: string, apiHost?: string, segmentsToCheck?: string[]) { + this._apiKey = apiKey ?? ''; + this._apiHost = apiHost ?? ''; this._segmentsToCheck = segmentsToCheck ?? []; } /** * Update the ODP configuration details - * @param apiKey Public API key for the ODP account - * @param apiHost Host of ODP audience segments API - * @param segmentsToCheck Audience segments + * @param {OdpConfig} config New ODP Config to potentially update self with * @returns true if configuration was updated successfully */ - public update(apiKey: string, apiHost: string, segmentsToCheck: string[]): boolean { - if (this._apiKey === apiKey && this._apiHost === apiHost && this._segmentsToCheck === segmentsToCheck) { + public update(config: OdpConfig): boolean { + if (this.equals(config)) { return false; } else { - this._apiKey = apiKey; - this._apiHost = apiHost; - this._segmentsToCheck = segmentsToCheck; + if (config.apiKey) this._apiKey = config.apiKey; + if (config.apiHost) this._apiHost = config.apiHost; + if (config.segmentsToCheck) this._segmentsToCheck = config.segmentsToCheck; return true; } @@ -88,4 +88,17 @@ export class OdpConfig { public isReady(): boolean { return !!this._apiKey && !!this._apiHost; } + + /** + * Detects if there are any changes between the current and incoming ODP Configs + * @param configToCompare ODP Configuration to check self against for equality + * @returns Boolean based on if the current ODP Config is equivalent to the incoming ODP Config + */ + public equals(configToCompare: OdpConfig): boolean { + return ( + this._apiHost === configToCompare._apiHost && + this._apiKey === configToCompare._apiKey && + checkArrayEquality(this.segmentsToCheck, configToCompare._segmentsToCheck) + ); + } } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 8ac7bb041..7eda72a3b 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,14 @@ */ import { LogHandler, LogLevel } from '../../modules/logging'; -import { OdpEvent } from './odp_event'; + import { uuid } from '../../utils/fns'; -import { ODP_USER_KEY } from '../../utils/enums'; +import { ERROR_MESSAGES, ODP_USER_KEY, ODP_EVENT_TYPE } from '../../utils/enums'; + +import { OdpEvent } from './odp_event'; import { OdpConfig } from './odp_config'; import { OdpEventApiManager } from './odp_event_api_manager'; +import { invalidOdpDataFound } from './odp_utils'; const MAX_RETRIES = 3; const DEFAULT_BATCH_SIZE = 10; @@ -145,11 +148,18 @@ export class OdpEventManager implements IOdpEventManager { } /** - * Update ODP configuration settings - * @param odpConfig New configuration to apply + * Update ODP configuration settings. + * @param newConfig New configuration to apply */ - public updateSettings(odpConfig: OdpConfig): void { - this.odpConfig = odpConfig; + public updateSettings(newConfig: OdpConfig): void { + this.odpConfig = newConfig; + } + + /** + * Cleans up all pending events; occurs every time the ODP Config is updated. + */ + public flush(): void { + this.processQueue(true); } /** @@ -181,23 +191,31 @@ export class OdpEventManager implements IOdpEventManager { const identifiers = new Map(); identifiers.set(ODP_USER_KEY.VUID, vuid); - const event = new OdpEvent('fullstack', 'client_initialized', identifiers); + const event = new OdpEvent(ODP_EVENT_TYPE, 'client_initialized', identifiers); this.sendEvent(event); } /** * Associate a full-stack userid with an established VUID - * @param userId Full-stack User ID - * @param vuid Visitor User ID + * @param {string} userId (Optional) Full-stack User ID + * @param {string} vuid (Optional) Visitor User ID */ - public identifyUser(userId: string, vuid?: string): void { + public identifyUser(userId?: string, vuid?: string): void { const identifiers = new Map(); + if (!userId && !vuid) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_UID_MISSING); + return; + } + if (vuid) { identifiers.set(ODP_USER_KEY.VUID, vuid); } - identifiers.set(ODP_USER_KEY.FS_USER_ID, userId); - const event = new OdpEvent('fullstack', 'identified', identifiers); + if (userId) { + identifiers.set(ODP_USER_KEY.FS_USER_ID, userId); + } + + const event = new OdpEvent(ODP_EVENT_TYPE, 'identified', identifiers); this.sendEvent(event); } @@ -206,7 +224,7 @@ export class OdpEventManager implements IOdpEventManager { * @param event ODP Event to forward */ public sendEvent(event: OdpEvent): void { - if (this.invalidDataFound(event.data)) { + if (invalidOdpDataFound(event.data)) { this.logger.log(LogLevel.ERROR, 'Event data found to be invalid.'); } else { event.data = this.augmentCommonData(event.data); @@ -374,23 +392,6 @@ export class OdpEventManager implements IOdpEventManager { return false; } - /** - * Validate event data value types - * @param data Event data to be validated - * @returns True if an invalid type was found in the data otherwise False - * @private - */ - private invalidDataFound(data: Map): boolean { - const validTypes: string[] = ['string', 'number', 'boolean']; - let foundInvalidValue = false; - data.forEach(value => { - if (!validTypes.includes(typeof value) && value !== null) { - foundInvalidValue = true; - } - }); - return foundInvalidValue; - } - /** * Add additional common data including an idempotent ID and execution context to event data * @param sourceData Existing event data to augment diff --git a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts new file mode 100644 index 000000000..b77a8de74 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts @@ -0,0 +1,246 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BROWSER_CLIENT_VERSION, LOG_MESSAGES } from './../../utils/enums/index'; +import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; + +import { RequestHandler } from './../../utils/http_request_handler/http'; +import { BrowserLRUCache } from './../../utils/lru_cache/browser_lru_cache'; +import { LRUCache } from './../../utils/lru_cache/lru_cache'; + +import { VuidManager } from '../../plugins/vuid_manager'; + +import { OdpConfig } from './odp_config'; +import { OdpEventManager } from './odp_event_manager'; +import { OdpSegmentManager } from './odp_segment_manager'; +import { OdpSegmentApiManager } from './odp_segment_api_manager'; +import { OdpEventApiManager } from './odp_event_api_manager'; +import { OptimizelySegmentOption } from './optimizely_segment_option'; +import { invalidOdpDataFound } from './odp_utils'; +import { OdpEvent } from './odp_event'; + +/** + * @param {boolean} disable Flag for disabling ODP Manager. + * @param {RequestHandler} requestHandler HTTP request handler that will be used by Segment and Event Managers. + * @param {LogHandler} logger (Optional) Accepts custom LogHandler. Defaults to the default global LogHandler. + * @param {string} clientEngine (Optional) String denoting specific client engine being used. Defaults to 'javascript-sdk'. + * @param {string} clientVersion (Optional) String denoting specific client version. Defaults to current version value from package.json. + * @param {LRUCache} segmentsCache (Optional) Accepts a custom LRUCache. Defaults to BrowserLRUCache. + * @param {OdpEventManager} eventManager (Optional) Accepts a custom ODPEventManager. + * @param {OdpSegmentManager} segmentManager (Optional) Accepts a custom ODPSegmentManager. + */ +interface OdpManagerConfig { + disable: boolean; + requestHandler: RequestHandler; + logger?: LogHandler; + clientEngine?: string; + clientVersion?: string; + segmentsCache?: LRUCache; + eventManager?: OdpEventManager; + segmentManager?: OdpSegmentManager; +} + +/** + * Orchestrates segments manager, event manager, and ODP configuration + */ +export class OdpManager { + enabled: boolean; + logger: LogHandler; + odpConfig: OdpConfig = new OdpConfig(); + + /** + * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. + * It fetches all qualified segments for the given user context and manages the segments cache for all user contexts. + */ + public segmentManager: OdpSegmentManager | undefined; + + /** + * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. + * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. + */ + public eventManager: OdpEventManager | undefined; + + constructor({ + disable, + requestHandler, + logger, + clientEngine, + clientVersion, + segmentsCache, + eventManager, + segmentManager, + }: OdpManagerConfig) { + this.enabled = !disable; + this.logger = logger || getLogger(); + + if (!this.enabled) { + this.logger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_ENABLED); + return; + } + + // Set up Segment Manager (Audience Segments GraphQL API Interface) + if (segmentManager) { + this.segmentManager = segmentManager; + this.segmentManager.updateSettings(this.odpConfig); + } else { + this.segmentManager = new OdpSegmentManager( + this.odpConfig, + segmentsCache || new BrowserLRUCache(), + new OdpSegmentApiManager(requestHandler, this.logger) + ); + } + + // Set up Events Manager (Events REST API Interface) + if (eventManager) { + this.eventManager = eventManager; + this.eventManager.updateSettings(this.odpConfig); + } else { + this.eventManager = new OdpEventManager({ + odpConfig: this.odpConfig, + apiManager: new OdpEventApiManager(requestHandler, this.logger), + logger: this.logger, + clientEngine: clientEngine || 'javascript-sdk', + clientVersion: clientVersion || BROWSER_CLIENT_VERSION, + }); + } + + this.eventManager.start(); + } + + /** + * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments + */ + public updateSettings({ apiKey, apiHost, segmentsToCheck }: OdpConfig): boolean { + if (!this.enabled) { + return false; + } + + if (!this.eventManager) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING); + return false; + } + + if (!this.segmentManager) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING); + return false; + } + + this.eventManager.flush(); + + const newConfig = new OdpConfig(apiKey, apiHost, segmentsToCheck); + const configDidUpdate = this.odpConfig.update(newConfig); + + if (configDidUpdate) { + this.odpConfig.update(newConfig); + this.segmentManager?.reset(); + return true; + } + + return false; + } + + /** + * Attempts to stop the current instance of ODP Manager's event manager, if it exists and is running. + */ + public close(): void { + if (!this.enabled) { + return; + } + + this.eventManager?.stop(); + } + + /** + * Attempts to fetch and return a list of a user's qualified segments from the local segments cache. + * If no cached data exists for the target user, this fetches and caches data from the ODP server instead. + * @param {string} userId - Unique identifier of a target user. + * @param {Array} options - An array of OptimizelySegmentOption used to ignore and/or reset the cache. + * @returns {Promise} A promise holding either a list of qualified segments or null. + */ + public async fetchQualifiedSegments( + userId: string, + options: Array = [] + ): Promise { + if (!this.enabled) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED); + return null; + } + + if (!this.segmentManager) { + throw new Error(ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING); + } + + if (VuidManager.isVuid(userId)) { + return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, userId, options); + } + + return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); + } + + /** + * Identifies a user via the ODP Event Manager + * @param {string} userId (Optional) Custom unique identifier of a target user. + * @param {string} vuid (Optional) Secondary unique identifier of a target user, primarily used by client SDKs. + * @returns + */ + public identifyUser(userId?: string, vuid?: string): void { + if (!this.enabled) { + this.logger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED); + return; + } + + if (!this.odpConfig.isReady()) { + this.logger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED); + return; + } + + if (!this.eventManager) { + throw new Error(ERROR_MESSAGES.ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING); + } + + if (userId && VuidManager.isVuid(userId)) { + this.eventManager.identifyUser(undefined, userId); + return; + } + + this.eventManager.identifyUser(userId, vuid); + } + + /** + * Sends an event to the ODP Server via the ODP Events API + * @param {OdpEvent} > ODP Event to send to event manager + */ + public sendEvent({ type, action, identifiers, data }: OdpEvent): void { + if (!this.enabled) { + throw new Error(ERROR_MESSAGES.ODP_NOT_ENABLED); + } + + if (!this.odpConfig.isReady()) { + throw new Error(ERROR_MESSAGES.ODP_NOT_INTEGRATED); + } + + if (invalidOdpDataFound(data)) { + throw new Error(ERROR_MESSAGES.ODP_INVALID_DATA); + } + + if (!this.eventManager) { + throw new Error(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING); + } + + this.eventManager.sendEvent(new OdpEvent(type, action, identifiers, data)); + } +} diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts index b3d86d354..8a4deb283 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,21 +21,50 @@ import { OdpSegmentApiManager } from './odp_segment_api_manager'; import { OdpConfig } from './odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; -// Schedules connections to ODP for audience segmentation and caches the results. +/** + * Schedules connections to ODP for audience segmentation and caches the results. + */ export class OdpSegmentManager { - odpConfig: OdpConfig; - segmentsCache: LRUCache>; - odpSegmentApiManager: OdpSegmentApiManager; - logger: LogHandler; + /** + * ODP configuration settings in used + * @private + */ + private odpConfig: OdpConfig; + + /** + * Holds cached audience segments + * @private + */ + private _segmentsCache: LRUCache; + + /** + * Getter for private segments cache + * @public + */ + public get segmentsCache(): LRUCache { + return this._segmentsCache; + } + + /** + * GraphQL API Manager used to fetch segments + * @private + */ + private odpSegmentApiManager: OdpSegmentApiManager; + + /** + * Handler for recording execution logs + * @private + */ + private readonly logger: LogHandler; constructor( odpConfig: OdpConfig, - segmentsCache: LRUCache>, + segmentsCache: LRUCache, odpSegmentApiManager: OdpSegmentApiManager, logger?: LogHandler ) { this.odpConfig = odpConfig; - this.segmentsCache = segmentsCache; + this._segmentsCache = segmentsCache; this.odpSegmentApiManager = odpSegmentApiManager; this.logger = logger || getLogger('OdpSegmentManager'); } @@ -52,7 +81,7 @@ export class OdpSegmentManager { userKey: ODP_USER_KEY, userValue: string, options: Array - ): Promise | null> { + ): Promise { const { apiHost: odpApiHost, apiKey: odpApiKey } = this.odpConfig; if (!odpApiKey || !odpApiHost) { @@ -74,7 +103,7 @@ export class OdpSegmentManager { if (resetCache) this.reset(); if (!ignoreCache && !resetCache) { - const cachedSegments = this.segmentsCache.lookup(cacheKey); + const cachedSegments = this._segmentsCache.lookup(cacheKey); if (cachedSegments) { this.logger.log(LogLevel.DEBUG, 'ODP cache hit. Returning segments from cache "%s".', cacheKey); return cachedSegments; @@ -92,7 +121,7 @@ export class OdpSegmentManager { segmentsToCheck ); - if (segments && !ignoreCache) this.segmentsCache.save({ key: cacheKey, value: segments }); + if (segments && !ignoreCache) this._segmentsCache.save({ key: cacheKey, value: segments }); return segments; } @@ -101,7 +130,7 @@ export class OdpSegmentManager { * Clears the segments cache */ reset(): void { - this.segmentsCache.reset(); + this._segmentsCache.reset(); } /** @@ -113,4 +142,12 @@ export class OdpSegmentManager { makeCacheKey(userKey: string, userValue: string): string { return `${userKey}-$-${userValue}`; } + + /** + * Updates the ODP Config settings of ODP Segment Manager + * @param config New ODP Config that will overwrite the existing config + */ + public updateSettings(config: OdpConfig): void { + this.odpConfig = config; + } } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_types.ts b/packages/optimizely-sdk/lib/core/odp/odp_types.ts index cb034a3c9..3fed03408 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_types.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/optimizely-sdk/lib/core/odp/odp_utils.ts b/packages/optimizely-sdk/lib/core/odp/odp_utils.ts new file mode 100644 index 000000000..875b7e091 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/odp/odp_utils.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Validate event data value types + * @param data Event data to be validated + * @returns True if an invalid type was found in the data otherwise False + * @private + */ +export function invalidOdpDataFound(data: Map): boolean { + const validTypes: string[] = ['string', 'number', 'boolean']; + let foundInvalidValue = false; + data.forEach(value => { + if (!validTypes.includes(typeof value) && value !== null) { + foundInvalidValue = true; + } + }); + return foundInvalidValue; +} diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/packages/optimizely-sdk/lib/core/project_config/index.tests.js index 479d7c18c..fa4c03d16 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -1,11 +1,11 @@ /** - * Copyright 2016-2022, Optimizely + * Copyright 2016-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -836,8 +836,8 @@ describe('lib/core/project_config', function () { }) it('should contain all expected unique odp segments in allSegments', () => { - assert.equal(config.allSegments.size, 3) - assert.deepEqual(config.allSegments, new Set(['odp-segment-1', 'odp-segment-2', 'odp-segment-3'])) + assert.equal(config.allSegments.length, 3) + assert.deepEqual(config.allSegments, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']) }) }); @@ -863,7 +863,7 @@ describe('lib/core/project_config', function () { }) it('should contain all expected unique odp segments in all segments', () => { - assert.equal(config.allSegments.size, 0) + assert.equal(config.allSegments.length, 0) }) }); diff --git a/packages/optimizely-sdk/lib/core/project_config/index.ts b/packages/optimizely-sdk/lib/core/project_config/index.ts index aa89cc566..5d3472c2b 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.ts +++ b/packages/optimizely-sdk/lib/core/project_config/index.ts @@ -1,11 +1,11 @@ /** - * Copyright 2016-2022, Optimizely + * Copyright 2016-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,21 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - find, - objectEntries, - objectValues, - sprintf, - assign, - keyBy -} from '../../utils/fns'; +import { find, objectEntries, objectValues, sprintf, assign, keyBy } from '../../utils/fns'; -import { - ERROR_MESSAGES, - LOG_LEVEL, - LOG_MESSAGES, - FEATURE_VARIABLE_TYPES, -} from '../../utils/enums'; +import { ERROR_MESSAGES, LOG_LEVEL, LOG_MESSAGES, FEATURE_VARIABLE_TYPES } from '../../utils/enums'; import configValidator from '../../utils/config_validator'; import { LogHandler } from '../../modules/logging'; @@ -51,7 +39,7 @@ interface TryCreatingProjectConfigConfig { // eslint-disable-next-line @typescript-eslint/ban-types datafile: string | object; jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, + validate(jsonObject: unknown): boolean; }; logger: LogHandler; } @@ -103,7 +91,7 @@ export interface ProjectConfig { integrationKeyMap?: { [key: string]: Integration }; publicKeyForOdp?: string; hostForOdp?: string; - allSegments: Set; + allSegments: string[]; } const EXPERIMENT_RUNNING_STATUS = 'Running'; @@ -124,14 +112,14 @@ function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { }); datafileCopy.groups = (datafile.groups || []).map((group: Group) => { const groupCopy = assign({}, group); - groupCopy.experiments = (group.experiments || []).map((experiment) => { + groupCopy.experiments = (group.experiments || []).map(experiment => { return assign({}, experiment); }); return groupCopy; }); datafileCopy.rollouts = (datafile.rollouts || []).map((rollout: Rollout) => { const rolloutCopy = assign({}, rollout); - rolloutCopy.experiments = (rollout.experiments || []).map((experiment) => { + rolloutCopy.experiments = (rollout.experiments || []).map(experiment => { return assign({}, experiment); }); return rolloutCopy; @@ -149,10 +137,7 @@ function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { * @param {string|null} datafileStr JSON string representation of the datafile * @return {ProjectConfig} Object representing project configuration */ -export const createProjectConfig = function ( - datafileObj?: JSON, - datafileStr: string | null = null -): ProjectConfig { +export const createProjectConfig = function(datafileObj?: JSON, datafileStr: string | null = null): ProjectConfig { const projectConfig = createMutationSafeDatafileCopy(datafileObj); projectConfig.__datafileStr = datafileStr === null ? JSON.stringify(datafileObj) : datafileStr; @@ -161,53 +146,54 @@ export const createProjectConfig = function ( * Conditions of audiences in projectConfig.typedAudiences are not * expected to be string-encoded as they are here in projectConfig.audiences. */ - (projectConfig.audiences || []).forEach((audience) => { + (projectConfig.audiences || []).forEach(audience => { audience.conditions = JSON.parse(audience.conditions as string); }); projectConfig.audiencesById = keyBy(projectConfig.audiences, 'id'); assign(projectConfig.audiencesById, keyBy(projectConfig.typedAudiences, 'id')); - projectConfig.allSegments = new Set([]) + projectConfig.allSegments = []; + const allSegmentsSet = new Set(); Object.keys(projectConfig.audiencesById) - .map((audience) => getAudienceSegments(projectConfig.audiencesById[audience])) + .map(audience => getAudienceSegments(projectConfig.audiencesById[audience])) .forEach(audienceSegments => { audienceSegments.forEach(segment => { - projectConfig.allSegments.add(segment) - }) - }) + allSegmentsSet.add(segment); + }); + }); + + projectConfig.allSegments = Array.from(allSegmentsSet); projectConfig.attributeKeyMap = keyBy(projectConfig.attributes, 'key'); projectConfig.eventKeyMap = keyBy(projectConfig.events, 'key'); projectConfig.groupIdMap = keyBy(projectConfig.groups, 'id'); let experiments; - Object.keys(projectConfig.groupIdMap || {}).forEach((Id) => { + Object.keys(projectConfig.groupIdMap || {}).forEach(Id => { experiments = projectConfig.groupIdMap[Id].experiments; - (experiments || []).forEach((experiment) => { + (experiments || []).forEach(experiment => { projectConfig.experiments.push(assign(experiment, { groupId: Id })); }); }); projectConfig.rolloutIdMap = keyBy(projectConfig.rollouts || [], 'id'); - objectValues(projectConfig.rolloutIdMap || {}).forEach( - (rollout) => { - (rollout.experiments || []).forEach((experiment) => { - projectConfig.experiments.push(experiment); - // Creates { : } map inside of the experiment - experiment.variationKeyMap = keyBy(experiment.variations, 'key'); - }); - } - ); + objectValues(projectConfig.rolloutIdMap || {}).forEach(rollout => { + (rollout.experiments || []).forEach(experiment => { + projectConfig.experiments.push(experiment); + // Creates { : } map inside of the experiment + experiment.variationKeyMap = keyBy(experiment.variations, 'key'); + }); + }); if (projectConfig.integrations) { projectConfig.integrationKeyMap = keyBy(projectConfig.integrations, 'key'); projectConfig.integrations - .filter((integration) => integration.key === 'odp') - .forEach((integration) => { - if (integration.publicKey) projectConfig.publicKeyForOdp = integration.publicKey - if (integration.host) projectConfig.hostForOdp = integration.host - }) + .filter(integration => integration.key === 'odp') + .forEach(integration => { + if (integration.publicKey) projectConfig.publicKeyForOdp = integration.publicKey; + if (integration.host) projectConfig.hostForOdp = integration.host; + }); } projectConfig.experimentKeyMap = keyBy(projectConfig.experiments, 'key'); @@ -215,13 +201,13 @@ export const createProjectConfig = function ( projectConfig.variationIdMap = {}; projectConfig.variationVariableUsageMap = {}; - (projectConfig.experiments || []).forEach((experiment) => { + (projectConfig.experiments || []).forEach(experiment => { // Creates { : } map inside of the experiment experiment.variationKeyMap = keyBy(experiment.variations, 'key'); // Creates { : { key: , id: } } mapping for quick lookup assign(projectConfig.variationIdMap, keyBy(experiment.variations, 'id')); - objectValues(experiment.variationKeyMap || {}).forEach((variation) => { + objectValues(experiment.variationKeyMap || {}).forEach(variation => { if (variation.variables) { projectConfig.variationVariableUsageMap[variation.id] = keyBy(variation.variables, 'id'); } @@ -233,28 +219,26 @@ export const createProjectConfig = function ( projectConfig.experimentFeatureMap = {}; projectConfig.featureKeyMap = keyBy(projectConfig.featureFlags || [], 'key'); - objectValues(projectConfig.featureKeyMap || {}).forEach( - (feature) => { - // Json type is represented in datafile as a subtype of string for the sake of backwards compatibility. - // Converting it to a first-class json type while creating Project Config - feature.variables.forEach((variable) => { - if (variable.type === FEATURE_VARIABLE_TYPES.STRING && variable.subType === FEATURE_VARIABLE_TYPES.JSON) { - variable.type = FEATURE_VARIABLE_TYPES.JSON as VariableType; - delete variable.subType; - } - }); + objectValues(projectConfig.featureKeyMap || {}).forEach(feature => { + // Json type is represented in datafile as a subtype of string for the sake of backwards compatibility. + // Converting it to a first-class json type while creating Project Config + feature.variables.forEach(variable => { + if (variable.type === FEATURE_VARIABLE_TYPES.STRING && variable.subType === FEATURE_VARIABLE_TYPES.JSON) { + variable.type = FEATURE_VARIABLE_TYPES.JSON as VariableType; + delete variable.subType; + } + }); - feature.variableKeyMap = keyBy(feature.variables, 'key'); - (feature.experimentIds || []).forEach((experimentId) => { - // Add this experiment in experiment-feature map. - if (projectConfig.experimentFeatureMap[experimentId]) { - projectConfig.experimentFeatureMap[experimentId].push(feature.id); - } else { - projectConfig.experimentFeatureMap[experimentId] = [feature.id]; - } - }); - } - ); + feature.variableKeyMap = keyBy(feature.variables, 'key'); + (feature.experimentIds || []).forEach(experimentId => { + // Add this experiment in experiment-feature map. + if (projectConfig.experimentFeatureMap[experimentId]) { + projectConfig.experimentFeatureMap[experimentId].push(feature.id); + } else { + projectConfig.experimentFeatureMap[experimentId] = [feature.id]; + } + }); + }); // all rules (experiment rules and delivery rules) for each flag projectConfig.flagRulesMap = {}; @@ -281,19 +265,17 @@ export const createProjectConfig = function ( // - we collect variations used in each rule (experiment rules and delivery rules) projectConfig.flagVariationsMap = {}; - objectEntries(projectConfig.flagRulesMap || {}).forEach( - ([flagKey, rules]) => { - const variations: OptimizelyVariation[] = []; - rules.forEach(rule => { - rule.variations.forEach(variation => { - if (!find(variations, item => item.id === variation.id)) { - variations.push(variation); - } - }); + objectEntries(projectConfig.flagRulesMap || {}).forEach(([flagKey, rules]) => { + const variations: OptimizelyVariation[] = []; + rules.forEach(rule => { + rule.variations.forEach(variation => { + if (!find(variations, item => item.id === variation.id)) { + variations.push(variation); + } }); - projectConfig.flagVariationsMap[flagKey] = variations; - } - ); + }); + projectConfig.flagVariationsMap[flagKey] = variations; + }); return projectConfig; }; @@ -303,8 +285,8 @@ export const createProjectConfig = function ( * @param {Audience} audience Object representing the audience being parsed * @return {string[]} List of all audience segments */ -export const getAudienceSegments = function (audience: Audience): string[] { - if (!audience.conditions) return [] +export const getAudienceSegments = function(audience: Audience): string[] { + if (!audience.conditions) return []; return getSegmentsFromConditions(audience.conditions); }; @@ -313,20 +295,18 @@ const getSegmentsFromConditions = (condition: any): string[] => { const segments = []; if (isLogicalOperator(condition)) { - return [] - } - else if (Array.isArray(condition)) { - condition.forEach((nextCondition) => segments.push(...getSegmentsFromConditions(nextCondition))) - } - else if (condition['match'] === 'qualified') { - segments.push(condition['value']) + return []; + } else if (Array.isArray(condition)) { + condition.forEach(nextCondition => segments.push(...getSegmentsFromConditions(nextCondition))); + } else if (condition['match'] === 'qualified') { + segments.push(condition['value']); } return segments; -} +}; function isLogicalOperator(condition: string): boolean { - return ['and', 'or', 'not'].includes(condition) + return ['and', 'or', 'not'].includes(condition); } /** @@ -336,7 +316,7 @@ function isLogicalOperator(condition: string): boolean { * @return {string} Experiment ID corresponding to the provided experiment key * @throws If experiment key is not in datafile */ -export const getExperimentId = function (projectConfig: ProjectConfig, experimentKey: string): string { +export const getExperimentId = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); @@ -351,7 +331,7 @@ export const getExperimentId = function (projectConfig: ProjectConfig, experimen * @return {string} Layer ID corresponding to the provided experiment key * @throws If experiment key is not in datafile */ -export const getLayerId = function (projectConfig: ProjectConfig, experimentId: string): string { +export const getLayerId = function(projectConfig: ProjectConfig, experimentId: string): string { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); @@ -366,7 +346,7 @@ export const getLayerId = function (projectConfig: ProjectConfig, experimentId: * @param {LogHandler} logger * @return {string|null} Attribute ID corresponding to the provided attribute key. Attribute key if it is a reserved attribute. */ -export const getAttributeId = function ( +export const getAttributeId = function( projectConfig: ProjectConfig, attributeKey: string, logger: LogHandler @@ -379,7 +359,7 @@ export const getAttributeId = function ( LOG_LEVEL.WARNING, 'Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.', attributeKey, - RESERVED_ATTRIBUTE_PREFIX, + RESERVED_ATTRIBUTE_PREFIX ); } return attribute.id; @@ -397,7 +377,7 @@ export const getAttributeId = function ( * @param {string} eventKey Event key for which ID is to be determined * @return {string|null} Event ID corresponding to the provided event key */ -export const getEventId = function (projectConfig: ProjectConfig, eventKey: string): string | null { +export const getEventId = function(projectConfig: ProjectConfig, eventKey: string): string | null { const event = projectConfig.eventKeyMap[eventKey]; if (event) { return event.id; @@ -412,7 +392,7 @@ export const getEventId = function (projectConfig: ProjectConfig, eventKey: stri * @return {string} Experiment status corresponding to the provided experiment key * @throws If experiment key is not in datafile */ -export const getExperimentStatus = function (projectConfig: ProjectConfig, experimentKey: string): string { +export const getExperimentStatus = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); @@ -426,7 +406,7 @@ export const getExperimentStatus = function (projectConfig: ProjectConfig, exper * @param {string} experimentKey Experiment key for which status is to be compared with 'Running' * @return {boolean} True if experiment status is set to 'Running', false otherwise */ -export const isActive = function (projectConfig: ProjectConfig, experimentKey: string): boolean { +export const isActive = function(projectConfig: ProjectConfig, experimentKey: string): boolean { return getExperimentStatus(projectConfig, experimentKey) === EXPERIMENT_RUNNING_STATUS; }; @@ -438,7 +418,7 @@ export const isActive = function (projectConfig: ProjectConfig, experimentKey: s * False if the experiment is not running * */ -export const isRunning = function (projectConfig: ProjectConfig, experimentKey: string): boolean { +export const isRunning = function(projectConfig: ProjectConfig, experimentKey: string): boolean { return getExperimentStatus(projectConfig, experimentKey) === EXPERIMENT_RUNNING_STATUS; }; @@ -451,7 +431,7 @@ export const isRunning = function (projectConfig: ProjectConfig, experimentKey: * Examples: ["5", "6"], ["and", ["or", "1", "2"], "3"] * @throws If experiment key is not in datafile */ -export const getExperimentAudienceConditions = function ( +export const getExperimentAudienceConditions = function( projectConfig: ProjectConfig, experimentId: string ): Array { @@ -469,7 +449,7 @@ export const getExperimentAudienceConditions = function ( * @param {string} variationId ID of the variation * @return {string|null} Variation key or null if the variation ID is not found */ -export const getVariationKeyFromId = function (projectConfig: ProjectConfig, variationId: string): string | null { +export const getVariationKeyFromId = function(projectConfig: ProjectConfig, variationId: string): string | null { if (projectConfig.variationIdMap.hasOwnProperty(variationId)) { return projectConfig.variationIdMap[variationId].key; } @@ -483,7 +463,7 @@ export const getVariationKeyFromId = function (projectConfig: ProjectConfig, var * @param {string} variationId ID of the variation * @return {Variation|null} Variation or null if the variation ID is not found */ -export const getVariationFromId = function (projectConfig: ProjectConfig, variationId: string): Variation | null { +export const getVariationFromId = function(projectConfig: ProjectConfig, variationId: string): Variation | null { if (projectConfig.variationIdMap.hasOwnProperty(variationId)) { return projectConfig.variationIdMap[variationId]; } @@ -498,7 +478,7 @@ export const getVariationFromId = function (projectConfig: ProjectConfig, variat * @param {string} variationKey The variation key * @return {string|null} Variation ID or null */ -export const getVariationIdFromExperimentAndVariationKey = function ( +export const getVariationIdFromExperimentAndVariationKey = function( projectConfig: ProjectConfig, experimentKey: string, variationKey: string @@ -518,7 +498,7 @@ export const getVariationIdFromExperimentAndVariationKey = function ( * @return {Experiment} Experiment * @throws If experiment key is not in datafile */ -export const getExperimentFromKey = function (projectConfig: ProjectConfig, experimentKey: string): Experiment { +export const getExperimentFromKey = function(projectConfig: ProjectConfig, experimentKey: string): Experiment { if (projectConfig.experimentKeyMap.hasOwnProperty(experimentKey)) { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (experiment) { @@ -536,7 +516,7 @@ export const getExperimentFromKey = function (projectConfig: ProjectConfig, expe * @return {TrafficAllocation[]} Traffic allocation for the experiment * @throws If experiment key is not in datafile */ -export const getTrafficAllocation = function (projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { +export const getTrafficAllocation = function(projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); @@ -552,7 +532,7 @@ export const getTrafficAllocation = function (projectConfig: ProjectConfig, expe * @param {LogHandler} logger * @return {Experiment|null} Experiment object or null */ -export const getExperimentFromId = function ( +export const getExperimentFromId = function( projectConfig: ProjectConfig, experimentId: string, logger: LogHandler @@ -569,18 +549,22 @@ export const getExperimentFromId = function ( }; /** -* Returns flag variation for specified flagKey and variationKey -* @param {flagKey} string -* @param {variationKey} string -* @return {Variation|null} -*/ -export const getFlagVariationByKey = function (projectConfig: ProjectConfig, flagKey: string, variationKey: string): Variation | null { + * Returns flag variation for specified flagKey and variationKey + * @param {flagKey} string + * @param {variationKey} string + * @return {Variation|null} + */ +export const getFlagVariationByKey = function( + projectConfig: ProjectConfig, + flagKey: string, + variationKey: string +): Variation | null { if (!projectConfig) { return null; } const variations = projectConfig.flagVariationsMap[flagKey]; - const result = find(variations, item => item.key === variationKey) + const result = find(variations, item => item.key === variationKey); if (result) { return result; } @@ -597,7 +581,7 @@ export const getFlagVariationByKey = function (projectConfig: ProjectConfig, fla * @return {FeatureFlag|null} Feature object, or null if no feature with the given * key exists */ -export const getFeatureFromKey = function ( +export const getFeatureFromKey = function( projectConfig: ProjectConfig, featureKey: string, logger: LogHandler @@ -624,7 +608,7 @@ export const getFeatureFromKey = function ( * @return {FeatureVariable|null} Variable object, or null one or both of the given * feature and variable keys are invalid */ -export const getVariableForFeature = function ( +export const getVariableForFeature = function( projectConfig: ProjectConfig, featureKey: string, variableKey: string, @@ -638,13 +622,7 @@ export const getVariableForFeature = function ( const variable = feature.variableKeyMap[variableKey]; if (!variable) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.VARIABLE_KEY_NOT_IN_DATAFILE, - MODULE_NAME, - variableKey, - featureKey, - ); + logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.VARIABLE_KEY_NOT_IN_DATAFILE, MODULE_NAME, variableKey, featureKey); return null; } @@ -663,7 +641,7 @@ export const getVariableForFeature = function ( * variation, or null if the given variable has no value * for the given variation or if the variation or variable are invalid */ -export const getVariableValueForVariation = function ( +export const getVariableValueForVariation = function( projectConfig: ProjectConfig, variable: FeatureVariable, variation: Variation, @@ -674,12 +652,7 @@ export const getVariableValueForVariation = function ( } if (!projectConfig.variationVariableUsageMap.hasOwnProperty(variation.id)) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, - MODULE_NAME, - variation.id, - ); + logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, MODULE_NAME, variation.id); return null; } @@ -705,7 +678,7 @@ export const getVariableValueForVariation = function ( * @returns {*} Variable value of the appropriate type, or * null if the type cast failed */ -export const getTypeCastValue = function ( +export const getTypeCastValue = function( variableValue: string, variableType: VariableType, logger: LogHandler @@ -715,13 +688,7 @@ export const getTypeCastValue = function ( switch (variableType) { case FEATURE_VARIABLE_TYPES.BOOLEAN: if (variableValue !== 'true' && variableValue !== 'false') { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } else { castValue = variableValue === 'true'; @@ -731,13 +698,7 @@ export const getTypeCastValue = function ( case FEATURE_VARIABLE_TYPES.INTEGER: castValue = parseInt(variableValue, 10); if (isNaN(castValue)) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } break; @@ -745,13 +706,7 @@ export const getTypeCastValue = function ( case FEATURE_VARIABLE_TYPES.DOUBLE: castValue = parseFloat(variableValue); if (isNaN(castValue)) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } break; @@ -759,14 +714,8 @@ export const getTypeCastValue = function ( case FEATURE_VARIABLE_TYPES.JSON: try { castValue = JSON.parse(variableValue); - } catch (e: any) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + } catch (e) { + logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } break; @@ -786,7 +735,7 @@ export const getTypeCastValue = function ( * @param {ProjectConfig} projectConfig * @returns {{ [id: string]: Audience }} */ -export const getAudiencesById = function (projectConfig: ProjectConfig): { [id: string]: Audience } { +export const getAudiencesById = function(projectConfig: ProjectConfig): { [id: string]: Audience } { return projectConfig.audiencesById; }; @@ -796,7 +745,7 @@ export const getAudiencesById = function (projectConfig: ProjectConfig): { [id: * @param {string} eventKey * @returns {boolean} */ -export const eventWithKeyExists = function (projectConfig: ProjectConfig, eventKey: string): boolean { +export const eventWithKeyExists = function(projectConfig: ProjectConfig, eventKey: string): boolean { return projectConfig.eventKeyMap.hasOwnProperty(eventKey); }; @@ -804,9 +753,9 @@ export const eventWithKeyExists = function (projectConfig: ProjectConfig, eventK * Returns true if experiment belongs to any feature, false otherwise. * @param {ProjectConfig} projectConfig * @param {string} experimentId - * @returns {boolean} + * @returns {boolean} */ -export const isFeatureExperiment = function (projectConfig: ProjectConfig, experimentId: string): boolean { +export const isFeatureExperiment = function(projectConfig: ProjectConfig, experimentId: string): boolean { return projectConfig.experimentFeatureMap.hasOwnProperty(experimentId); }; @@ -815,9 +764,9 @@ export const isFeatureExperiment = function (projectConfig: ProjectConfig, exper * @param {ProjectConfig} projectConfig * @returns {string} */ -export const toDatafile = function (projectConfig: ProjectConfig): string { +export const toDatafile = function(projectConfig: ProjectConfig): string { return projectConfig.__datafileStr; -} +}; /** * @typedef {Object} @@ -837,13 +786,13 @@ export const toDatafile = function (projectConfig: ProjectConfig): string { * @param {Object} config.logger * @returns {Object} Object containing configObj and error properties */ -export const tryCreatingProjectConfig = function ( +export const tryCreatingProjectConfig = function( config: TryCreatingProjectConfigConfig ): { configObj: ProjectConfig | null; error: Error | null } { let newDatafileObj; try { newDatafileObj = configValidator.validateDatafile(config.datafile); - } catch (error: any) { + } catch (error) { return { configObj: null, error }; } @@ -851,7 +800,7 @@ export const tryCreatingProjectConfig = function ( try { config.jsonSchemaValidator.validate(newDatafileObj); config.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_DATAFILE, MODULE_NAME); - } catch (error : any) { + } catch (error) { return { configObj: null, error }; } } else { @@ -877,9 +826,9 @@ export const tryCreatingProjectConfig = function ( * @param {ProjectConfig} projectConfig * @return {boolean} A boolean value that indicates if we should send flag decisions */ -export const getSendFlagDecisionsValue = function (projectConfig: ProjectConfig): boolean { +export const getSendFlagDecisionsValue = function(projectConfig: ProjectConfig): boolean { return !!projectConfig.sendFlagDecisions; -} +}; export default { createProjectConfig, diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index 69822f46c..c2417b46b 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2020, 2022 Optimizely + * Copyright 2016-2020, 2022-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts index 7d11a14cd..fcf2c0efd 100644 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ b/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,9 @@ import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE } fr import BackoffController from './backoffController'; import PersistentKeyValueCache from './persistentKeyValueCache'; +import { NotificationRegistry } from './../../core/notification_center/notification_registry'; +import { NOTIFICATION_TYPES } from '../../../lib/utils/enums'; + const logger = getLogger('DatafileManager'); const UPDATE_EVT = 'update'; @@ -95,6 +98,8 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana private cache: PersistentKeyValueCache; + private sdkKey: string; + // When true, this means the update interval timeout fired before the current // sync completed. In that case, we should sync again immediately upon // completion of the current request, instead of waiting another update @@ -117,6 +122,7 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana this.cache = cache; this.cacheKey = 'opt-datafile-' + sdkKey; + this.sdkKey = sdkKey; this.isReadyPromiseSettled = false; this.readyPromiseResolver = (): void => {}; this.readyPromiseRejecter = (): void => {}; @@ -232,6 +238,9 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana const datafileUpdate: DatafileUpdate = { datafile, }; + NotificationRegistry.getNotificationCenter(this.sdkKey, logger)?.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE + ); this.emitter.emit(UPDATE_EVT, datafileUpdate); } } diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index d6fdd8beb..8d6b6ccd4 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1,11 +1,11 @@ /**************************************************************************** - * Copyright 2020-2022, Optimizely, Inc. and contributors * + * Copyright 2020-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * - * http://www.apache.org/licenses/LICENSE-2.0 * + * https://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * @@ -13,11 +13,15 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ + import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; import { EventProcessor } from '../../lib/modules/event_processor'; +import { OdpManager } from '../core/odp/odp_manager'; +import { OdpConfig } from '../core/odp/odp_config'; + import { UserAttributes, EventTags, @@ -29,7 +33,7 @@ import { FeatureVariable, OptimizelyOptions, OptimizelyDecideOption, - OptimizelyDecision + OptimizelyDecision, } from '../shared_types'; import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; @@ -37,6 +41,7 @@ import { createProjectConfigManager, ProjectConfigManager } from '../core/projec import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; import { getImpressionEvent, getConversionEvent } from '../core/event_builder'; import { buildImpressionEvent, buildConversionEvent } from '../core/event_builder/event_helpers'; +import { NotificationRegistry } from '../core/notification_center/notification_registry'; import fns from '../utils/fns' import { validate } from '../utils/attributes_validator'; import * as enums from '../utils/enums'; @@ -81,6 +86,7 @@ export default class Optimizely { private decisionService: DecisionService; private eventProcessor: EventProcessor; private defaultDecideOptions: { [key: string]: boolean }; + private odpManager?: OdpManager; public notificationCenter: NotificationCenter; constructor(config: OptimizelyOptions) { @@ -168,13 +174,27 @@ export default class Optimizely { const eventProcessorStartedPromise = this.eventProcessor.start(); - this.readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then(function(promiseResults) { + this.readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then((promiseResults) => { + if (config.odpManager != null) { + this.odpManager = config.odpManager; + this.odpManager.eventManager?.start(); + this.updateODPSettings(); + const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; + if (sdkKey != null) { + NotificationRegistry.getNotificationCenter(sdkKey, this.logger) + ?.addNotificationListener(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, () => this.updateODPSettings()); + } else { + this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE); + } + } + // Only return status from project config promise because event processor promise does not return any status. return promiseResults[0]; }) this.readyTimeouts = {}; this.nextReadyTimeoutId = 0; + } /** @@ -1315,6 +1335,12 @@ export default class Optimizely { */ close(): Promise<{ success: boolean; reason?: string }> { try { + this.notificationCenter.clearAllNotificationListeners(); + const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; + if (sdkKey) { + NotificationRegistry.removeNotificationCenter(sdkKey); + } + const eventProcessorStoppedPromise = this.eventProcessor.stop(); if (this.disposeOnUpdate) { this.disposeOnUpdate(); @@ -1672,4 +1698,15 @@ export default class Optimizely { return this.decideForKeys(user, allFlagKeys, options); } + /** + * Updates ODP Config with most recent ODP key, host, and segments from the project config + */ + updateODPSettings(): void { + const projectConfig = this.projectConfigManager.getConfig(); + if (this.odpManager != null && projectConfig != null) { + this.odpManager.updateSettings( + new OdpConfig(projectConfig.publicKeyForOdp, projectConfig.hostForOdp, projectConfig.allSegments) + ); + } + } } diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts b/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts index eb602c371..2f1926d4f 100644 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts +++ b/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021, Optimizely + * Copyright 2021, 2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DatafileManager, DatafileUpdateListener} from '../../shared_types'; +import { DatafileManager, DatafileUpdateListener } from '../../shared_types'; +/** + * No-operation Datafile Manager for Lite Bundle designed for Edge platforms + * https://github.com/optimizely/javascript-sdk/issues/699 + */ class NoOpDatafileManager implements DatafileManager { - /* eslint-disable @typescript-eslint/no-unused-vars */ on(_eventName: string, _listener: DatafileUpdateListener): () => void { - return (): void => {} + return (): void => {}; } get(): string { diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts new file mode 100644 index 000000000..ae2bcfd70 --- /dev/null +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts @@ -0,0 +1,123 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BROWSER_CLIENT_VERSION, ERROR_MESSAGES, JAVASCRIPT_CLIENT_ENGINE, ODP_USER_KEY } from '../../utils/enums'; +import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +import { BrowserRequestHandler } from './../../utils/http_request_handler/browser_request_handler'; + +import BrowserAsyncStorageCache from '../key_value_cache/browserAsyncStorageCache'; +import PersistentKeyValueCache from '../key_value_cache/persistentKeyValueCache'; +import { BrowserLRUCache, LRUCache } from '../../utils/lru_cache'; + +import { VuidManager } from './../vuid_manager/index'; + +import { OdpManager } from '../../core/odp/odp_manager'; +import { OdpEvent } from '../../core/odp/odp_event'; +import { OdpEventManager } from '../../core/odp/odp_event_manager'; +import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; + +interface BrowserOdpManagerConfig { + disable: boolean; + logger?: LogHandler; + segmentsCache?: LRUCache; + eventManager?: OdpEventManager; + segmentManager?: OdpSegmentManager; +} + +// Client-side Browser Plugin for ODP Manager +export class BrowserOdpManager extends OdpManager { + static cache = new BrowserAsyncStorageCache(); + vuid?: string; + + constructor({ disable, logger, segmentsCache, eventManager, segmentManager }: BrowserOdpManagerConfig) { + const browserLogger = logger || getLogger(); + + const browserRequestHandler = new BrowserRequestHandler(browserLogger); + const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; + const browserClientVersion = BROWSER_CLIENT_VERSION; + + super({ + disable, + requestHandler: browserRequestHandler, + logger: browserLogger, + clientEngine: browserClientEngine, + clientVersion: browserClientVersion, + segmentsCache: segmentsCache || new BrowserLRUCache(), + eventManager, + segmentManager, + }); + + this.logger = browserLogger; + this.initializeVuid(BrowserOdpManager.cache); + } + + /** + * Upon initializing BrowserOdpManager, accesses or creates new VUID from Browser cache and registers it via the Event Manager + */ + private async initializeVuid(cache: PersistentKeyValueCache): Promise { + const vuidManager = await VuidManager.instance(cache); + this.vuid = vuidManager.vuid; + this.registerVuid(this.vuid); + } + + private registerVuid(vuid: string) { + if (!this.eventManager) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING); + return; + } + + try { + this.eventManager.registerVuid(vuid); + } catch (e) { + this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED); + } + } + + /** + * @override + * - Still identifies a user via the ODP Event Manager + * - Additionally, also passes VUID to help identify client-side users + * @param fsUserId Unique identifier of a target user. + */ + public identifyUser(fsUserId?: string, vuid?: string): void { + if (fsUserId && VuidManager.isVuid(fsUserId)) { + super.identifyUser(undefined, fsUserId); + return; + } + + super.identifyUser(fsUserId, vuid); + } + + /** + * @override + * - Sends an event to the ODP Server via the ODP Events API + * - Intercepts identifiers and injects VUID before sending event + * @param {OdpEvent} odpEvent > ODP Event to send to event manager + */ + public sendEvent({ type, action, identifiers, data }: OdpEvent): void { + const identifiersWithVuid = new Map(identifiers); + + if (!identifiers.has(ODP_USER_KEY.VUID)) { + if (this.vuid) { + identifiersWithVuid.set(ODP_USER_KEY.VUID, this.vuid); + } else { + throw new Error(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING); + } + } + + super.sendEvent({ type, action, identifiers: identifiersWithVuid, data }); + } +} diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts new file mode 100644 index 000000000..186f6925d --- /dev/null +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts @@ -0,0 +1,59 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NodeRequestHandler } from '../../utils/http_request_handler/node_request_handler'; + +import { LRUCache } from '../../utils/lru_cache'; +import { ServerLRUCache } from './../../utils/lru_cache/server_lru_cache'; + +import { OdpManager } from '../../core/odp/odp_manager'; +import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { OdpEventManager } from '../../core/odp/odp_event_manager'; +import { getLogger, LogHandler } from '../../modules/logging'; +import { ERROR_MESSAGES, LOG_LEVEL, NODE_CLIENT_ENGINE, NODE_CLIENT_VERSION } from '../../utils/enums'; + +interface NodeOdpManagerConfig { + disable: boolean; + logger?: LogHandler; + segmentsCache?: LRUCache; + segmentManager?: OdpSegmentManager; + eventManager?: OdpEventManager; +} + +/** + * Server-side Node Plugin for ODP Manager. + * Note: As this is still a work-in-progress. Please avoid using the Node ODP Manager. + */ +export class NodeOdpManager extends OdpManager { + constructor({ disable, logger, segmentsCache, segmentManager, eventManager }: NodeOdpManagerConfig) { + const nodeLogger = logger || getLogger(); + + const nodeRequestHandler = new NodeRequestHandler(nodeLogger); + const nodeClientEngine = NODE_CLIENT_ENGINE; + const nodeClientVersion = NODE_CLIENT_VERSION; + + super({ + disable, + requestHandler: nodeRequestHandler, + logger: nodeLogger, + clientEngine: nodeClientEngine, + clientVersion: nodeClientVersion, + segmentsCache: segmentsCache || new ServerLRUCache(), + segmentManager, + eventManager, + }); + } +} diff --git a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts b/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts index 81b5cf4bd..4518006c2 100644 --- a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts +++ b/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,17 +26,18 @@ export interface IVuidManager { */ export class VuidManager implements IVuidManager { /** - * Unique key used within the persistent value cache against which to - * store the VUID - * @private + * Prefix used as part of the VUID format + * @public + * @readonly */ - private _keyForVuid = 'optimizely-vuid'; + static readonly vuid_prefix: string = `vuid_`; /** - * Prefix used as part of the VUID format + * Unique key used within the persistent value cache against which to + * store the VUID * @private */ - private readonly _prefix: string = `vuid_`; + private _keyForVuid = 'optimizely-vuid'; /** * Current VUID value being used @@ -85,7 +86,7 @@ export class VuidManager implements IVuidManager { */ private async load(cache: PersistentKeyValueCache): Promise { const cachedValue = await cache.get(this._keyForVuid); - if (cachedValue && this.isVuid(cachedValue)) { + if (cachedValue && VuidManager.isVuid(cachedValue)) { this._vuid = cachedValue; } else { this._vuid = this.makeVuid(); @@ -100,14 +101,14 @@ export class VuidManager implements IVuidManager { * @returns A new visitor unique identifier */ private makeVuid(): string { - const maxLength = 32; // required by ODP server + const maxLength = 32; // required by ODP server // make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details. const uuidV4 = uuid(); const formatted = uuidV4.replace(/-/g, '').toLowerCase(); - const vuidFull = `${(this._prefix)}${formatted}`; + const vuidFull = `${VuidManager.vuid_prefix}${formatted}`; - return (vuidFull.length <= maxLength) ? vuidFull : vuidFull.substring(0, maxLength); + return vuidFull.length <= maxLength ? vuidFull : vuidFull.substring(0, maxLength); } /** @@ -124,7 +125,7 @@ export class VuidManager implements IVuidManager { * @param vuid VistorId to check * @returns *true* if the VisitorId is valid otherwise *false* for invalid */ - private isVuid = (vuid: string): boolean => vuid.startsWith(this._prefix); + static isVuid = (vuid: string): boolean => vuid.startsWith(VuidManager.vuid_prefix); /** * Function used in unit testing to reset the VuidManager diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index f3ff5251b..29122436d 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2022, Optimizely + * Copyright 2020-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,9 @@ */ import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from '../lib/modules/logging'; import { EventProcessor } from '../lib/modules/event_processor'; +import { OdpManager } from './core/odp/odp_manager'; -import { NotificationCenter as NotificationCenterImpl } from './core/notification_center' +import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; import { NOTIFICATION_TYPES } from './utils/enums'; export interface BucketerParams { @@ -41,11 +42,10 @@ export type UserAttributes = { // TODO[OASIS-6649]: Don't use any type // eslint-disable-next-line @typescript-eslint/no-explicit-any [name: string]: any; -} +}; export interface ExperimentBucketMap { - [experiment_id: string]: - { variation_id: string } + [experiment_id: string]: { variation_id: string }; } // Information about past bucketing decisions for a user. @@ -64,7 +64,7 @@ export interface UserProfileService { } export interface DatafileManagerConfig { - sdkKey: string, + sdkKey: string; datafile?: string; } @@ -114,7 +114,7 @@ export interface EventDispatcher { * After the event has at least been queued for dispatch, call this function to return * control back to the Client. */ - dispatchEvent: (event: Event, callback: (response: { statusCode: number; }) => void) => void; + dispatchEvent: (event: Event, callback: (response: { statusCode: number }) => void) => void; } export interface VariationVariable { @@ -164,9 +164,9 @@ export interface FeatureFlag { rolloutId: string; key: string; id: string; - experimentIds: string[], - variables: FeatureVariable[], - variableKeyMap: { [key: string]: FeatureVariable } + experimentIds: string[]; + variables: FeatureVariable[]; + variableKeyMap: { [key: string]: FeatureVariable }; groupId?: string; } @@ -175,7 +175,7 @@ export type Condition = { type: string; match?: string; value: string | number | boolean | null; -} +}; export interface Audience { id: string; @@ -214,7 +214,7 @@ export interface Group { } export interface FeatureKeyMap { - [key: string]: FeatureFlag + [key: string]: FeatureFlag; } export interface OnReadyResult { @@ -224,7 +224,7 @@ export interface OnReadyResult { export type ObjectWithUnknownProperties = { [key: string]: unknown; -} +}; export interface Rollout { id: string; @@ -237,7 +237,7 @@ export enum OptimizelyDecideOption { ENABLED_FLAGS_ONLY = 'ENABLED_FLAGS_ONLY', IGNORE_USER_PROFILE_SERVICE = 'IGNORE_USER_PROFILE_SERVICE', INCLUDE_REASONS = 'INCLUDE_REASONS', - EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES' + EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES', } /** @@ -255,12 +255,13 @@ export interface OptimizelyOptions { eventProcessor: EventProcessor; isValidInstance: boolean; jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, + validate(jsonObject: unknown): boolean; }; logger: LoggerFacade; sdkKey?: string; userProfileService?: UserProfileService | null; defaultDecideOptions?: OptimizelyDecideOption[]; + odpManager?: OdpManager; notificationCenter: NotificationCenterImpl; } @@ -285,43 +286,15 @@ export interface OptimizelyVariable { export interface Client { notificationCenter: NotificationCenter; - createUserContext( - userId: string, - attributes?: UserAttributes - ): OptimizelyUserContext | null; - activate( - experimentKey: string, - userId: string, - attributes?: UserAttributes - ): string | null; - track( - eventKey: string, - userId: string, - attributes?: UserAttributes, - eventTags?: EventTags - ): void; - getVariation( - experimentKey: string, - userId: string, - attributes?: UserAttributes - ): string | null; + createUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null; + activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null; + track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void; + getVariation(experimentKey: string, userId: string, attributes?: UserAttributes): string | null; setForcedVariation(experimentKey: string, userId: string, variationKey: string | null): boolean; getForcedVariation(experimentKey: string, userId: string): string | null; - isFeatureEnabled( - featureKey: string, - userId: string, - attributes?: UserAttributes - ): boolean; - getEnabledFeatures( - userId: string, - attributes?: UserAttributes - ): string[]; - getFeatureVariable( - featureKey: string, - variableKey: string, - userId: string, - attributes?: UserAttributes - ): unknown; + isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean; + getEnabledFeatures(userId: string, attributes?: UserAttributes): string[]; + getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): unknown; getFeatureVariableBoolean( featureKey: string, variableKey: string, @@ -346,12 +319,7 @@ export interface Client { userId: string, attributes?: UserAttributes ): string | null; - getFeatureVariableJSON( - featureKey: string, - variableKey: string, - userId: string, - attributes?: UserAttributes - ): unknown; + getFeatureVariableJSON(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): unknown; getAllFeatureVariables( featureKey: string, userId: string, @@ -379,16 +347,12 @@ export interface TrackListenerPayload extends ListenerPayload { * For compatibility with the previous declaration file */ export interface Config extends ConfigLite { - // options for Datafile Manager - datafileOptions?: DatafileOptions; - // limit of events to dispatch in a batch - eventBatchSize?: number; - // maximum time for an event to stay in the queue - eventFlushInterval?: number; - // maximum size for the event queue - eventMaxQueueSize?: number; - // sdk key + datafileOptions?: DatafileOptions; // Options for Datafile Manager + eventBatchSize?: number; // Maximum size of events to be dispatched in a batch + eventFlushInterval?: number; // Maximum time for an event to be enqueued + eventMaxQueueSize?: number; // Maximum size for the event queue sdkKey?: string; + odpManager?: OdpManager; } /** @@ -406,7 +370,7 @@ export interface ConfigLite { eventDispatcher?: EventDispatcher; // The object to validate against the schema jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, + validate(jsonObject: unknown): boolean; }; // level of logging i.e debug, info, error, warning etc logLevel?: LogLevel | string; @@ -422,15 +386,15 @@ export interface ConfigLite { export type OptimizelyExperimentsMap = { [experimentKey: string]: OptimizelyExperiment; -} +}; export type OptimizelyVariablesMap = { [variableKey: string]: OptimizelyVariable; -} +}; export type OptimizelyFeaturesMap = { [featureKey: string]: OptimizelyFeature; -} +}; export type OptimizelyAttribute = { id: string; @@ -493,17 +457,9 @@ export interface OptimizelyUserContext { getUserId(): string; getAttributes(): UserAttributes; setAttribute(key: string, value: unknown): void; - decide( - key: string, - options?: OptimizelyDecideOption[] - ): OptimizelyDecision; - decideForKeys( - keys: string[], - options?: OptimizelyDecideOption[], - ): { [key: string]: OptimizelyDecision }; - decideAll( - options?: OptimizelyDecideOption[], - ): { [key: string]: OptimizelyDecision }; + decide(key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision; + decideForKeys(keys: string[], options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; + decideAll(options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; trackEvent(eventName: string, eventTags?: EventTags): void; setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean; getForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null; diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index d8931acde..3911aac58 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2022, Optimizely, Inc. and contributors * + * Copyright 2016-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -31,6 +31,8 @@ export const ERROR_MESSAGES = { EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER: '%s: Audience segments fetch failed. (invalid identifier)', + FETCH_SEGMENTS_FAILED_NETWORK_ERROR: '%s: Audience segments fetch failed. (network error)', + FETCH_SEGMENTS_FAILED_DECODE_ERROR: '%s: Audience segments fetch failed. (decode error)', IMPROPERLY_FORMATTED_EXPERIMENT: '%s: Experiment key %s is improperly formatted.', INVALID_ATTRIBUTES: '%s: Provided attributes are in an invalid format.', INVALID_BUCKETING_ID: '%s: Unable to generate hash for bucketing ID %s: %s', @@ -51,6 +53,28 @@ export const ERROR_MESSAGES = { NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', + ODP_EVENT_FAILED: '%s: ODP event send failed (invalid url)', + ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING: + '%s: ODP unable to fetch qualified segments (Segments Manager not initialized).', + ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING: + '%s: ODP identify event %s is not dispatched (Event Manager not instantiated).', + ODP_INITIALIZATION_FAILED: '%s: ODP failed to initialize.', + ODP_INVALID_DATA: '%s: ODP data is not valid', + ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING: + '%s: ODP Manager failed to update OdpConfig settings for internal event manager. (Event Manager not initialized).', + ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING: + '%s: ODP Manager failed to update OdpConfig settings for internal segments manager. (Segments Manager not initialized).', + ODP_NOT_ENABLED: '%s: ODP is not enabled', + ODP_NOT_INTEGRATED: '%s: ODP is not integrated', + ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING: + '%s: ODP send event %s was not dispatched (Event Manager not instantiated).', + ODP_SEND_EVENT_FAILED_UID_MISSING: '%s: ODP send event %s was not dispatched (No valid user identifier provided).', + ODP_SEND_EVENT_FAILED_VUID_MISSING: '%s: ODP send event %s was not dispatched (Unable to fetch VUID).', + ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE: + '%s: You must provide an sdkKey. Cannot start Notification Center for ODP Integration.', + ODP_VUID_INITIALIZATION_FAILED: '%s: ODP VUID initialization failed.', + ODP_VUID_REGISTRATION_FAILED: '%s: ODP VUID failed to be registered.', + ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING: '%s: ODP register vuid failed. (Event Manager not instantiated).', UNDEFINED_ATTRIBUTE: '%s: Provided attribute: %s has an undefined value.', UNRECOGNIZED_ATTRIBUTE: '%s: Unrecognized attribute %s provided. Pruning before sending event to Optimizely.', UNABLE_TO_CAST_VALUE: '%s: Unable to cast value %s to type %s, returning null.', @@ -87,6 +111,8 @@ export const LOG_MESSAGES = { NO_ROLLOUT_EXISTS: '%s: There is no rollout of feature %s.', NOT_ACTIVATING_USER: '%s: Not activating user %s for experiment %s.', NOT_TRACKING_USER: '%s: Not tracking user %s.', + ODP_IDENTIFY_FAILED_ODP_DISABLED: '%s: ODP identify event for user %s is not dispatched (ODP disabled).', + ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED: '%s: ODP identify event %s is not dispatched (ODP not integrated).', PARSED_REVENUE_VALUE: '%s: Parsed revenue value "%s" from event tags.', PARSED_NUMERIC_VALUE: '%s: Parsed event value "%s" from event tags.', RETURNING_STORED_VARIATION: @@ -185,6 +211,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; +export const BROWSER_CLIENT_VERSION = '4.9.2'; export const NODE_CLIENT_VERSION = '4.9.2'; export const DECISION_NOTIFICATION_TYPES = { @@ -303,9 +330,19 @@ export enum NOTIFICATION_TYPES { export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute /** - * ODP User Key + * ODP User Key Options */ export enum ODP_USER_KEY { VUID = 'vuid', FS_USER_ID = 'fs_user_id', } + +export const ODP_EVENT_TYPE = 'fullstack'; + +/** + * ODP Event Action Options + */ +export enum ODP_EVENT_ACTION { + IDENTIFIED = 'identified', + INITIALIZED = 'client_initialized', +} diff --git a/packages/optimizely-sdk/lib/utils/fns/index.ts b/packages/optimizely-sdk/lib/utils/fns/index.ts index c769f147b..6852e030d 100644 --- a/packages/optimizely-sdk/lib/utils/fns/index.ts +++ b/packages/optimizely-sdk/lib/utils/fns/index.ts @@ -153,8 +153,21 @@ export function sprintf(format: string, ...args: any[]): string { }) } + + +/** + * Checks two string arrays for equality. + * @param arrayA First Array to be compared against. + * @param arrayB Second Array to be compared against. + * @returns {boolean} True if both arrays are equal, otherwise returns false. + */ +export function checkArrayEquality(arrayA: string[], arrayB: string[]): boolean { + return arrayA.length === arrayB.length && arrayA.every((item, index) => item === arrayB[index]); +} + export default { assign, + checkArrayEquality, currentTimestamp, isSafeInteger, keyBy, diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts index 23daedf01..f66ad86ba 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,18 @@ * limitations under the License. */ -import LRUCache from './lru_cache'; +import LRUCache, { ISegmentsCacheConfig } from './lru_cache'; + +export const BrowserLRUCacheConfig: ISegmentsCacheConfig = { + DEFAULT_CAPACITY: 100, + DEFAULT_TIMEOUT_SECS: 600, +}; export class BrowserLRUCache extends LRUCache { constructor() { super({ - maxSize: 100, - timeout: 600 * 1000, // 600 secs + maxSize: BrowserLRUCacheConfig.DEFAULT_CAPACITY, + timeout: BrowserLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, }); } } diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts index b0b2a60f5..dc4d4c94f 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,4 +105,9 @@ export class LRUCache { } } +export interface ISegmentsCacheConfig { + DEFAULT_CAPACITY: number; + DEFAULT_TIMEOUT_SECS: number; +} + export default LRUCache; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts index f39e15894..a933832f5 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,18 @@ * limitations under the License. */ -import LRUCache from './lru_cache'; +import LRUCache, { ISegmentsCacheConfig } from './lru_cache'; + +export const ServerLRUCacheConfig: ISegmentsCacheConfig = { + DEFAULT_CAPACITY: 10000, + DEFAULT_TIMEOUT_SECS: 600, +}; export class ServerLRUCache extends LRUCache { constructor() { super({ - maxSize: 10000, - timeout: 600 * 1000, // 600 secs + maxSize: ServerLRUCacheConfig.DEFAULT_CAPACITY, + timeout: ServerLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, }); } } diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index 13f70d1e9..ffa223b15 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,8 @@ * limitations under the License. */ +import { ODP_EVENT_ACTION, ODP_EVENT_TYPE } from './../lib/utils/enums/index'; + import { OdpConfig } from '../lib/core/odp/odp_config'; import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; @@ -422,8 +424,8 @@ describe('OdpEventManager', () => { expect(method).toEqual('POST'); const events = JSON.parse(data as string); const event = events[0]; - expect(event.type).toEqual('fullstack'); - expect(event.action).toEqual('identified'); + expect(event.type).toEqual(ODP_EVENT_TYPE); + expect(event.action).toEqual(ODP_EVENT_ACTION.IDENTIFIED); expect(event.identifiers).toEqual({ vuid: vuid, fs_user_id: fsUserId }); expect(event.data.idempotence_id.length).toBe(36); // uuid length expect(event.data.data_source_type).toEqual('sdk'); @@ -448,7 +450,7 @@ describe('OdpEventManager', () => { expect(eventManager['odpConfig'].apiKey).toEqual(apiKey); expect(eventManager['odpConfig'].apiHost).toEqual(apiHost); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(segmentsToCheck[0]); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(segmentsToCheck[1]); + expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[0]); + expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[1]); }); }); diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts new file mode 100644 index 000000000..536d91085 --- /dev/null +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -0,0 +1,219 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { anything, capture, instance, mock, resetCalls, verify } from 'ts-mockito'; + +import { LOG_MESSAGES } from './../lib/utils/enums/index'; +import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; + +import { LogHandler, LogLevel } from '../lib/modules/logging'; +import { RequestHandler } from '../lib/utils/http_request_handler/http'; +import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; + +import { BrowserOdpManager } from './../lib/plugins/odp_manager/index.browser'; +import { OdpConfig } from '../lib/core/odp/odp_config'; +import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; +import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; +import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; +import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; +import { VuidManager } from '../lib/plugins/vuid_manager'; + +const keyA = 'key-a'; +const hostA = 'host-a'; +const segmentsA = ['a']; +const userA = 'fs-user-a'; +const vuidA = 'vuid_a'; +const odpConfigA = new OdpConfig(keyA, hostA, segmentsA); + +const keyB = 'key-b'; +const hostB = 'host-b'; +const segmentsB = ['b']; +const userB = 'fs-user-b'; +const vuidB = 'vuid_b'; +const odpConfigB = new OdpConfig(keyB, hostB, segmentsB); + +describe('OdpManager', () => { + let mockLogger: LogHandler; + let mockRequestHandler: RequestHandler; + + let odpConfig: OdpConfig; + let logger: LogHandler; + let requestHandler: RequestHandler; + + let mockEventApiManager: OdpEventApiManager; + let mockEventManager: OdpEventManager; + let mockSegmentApiManager: OdpSegmentApiManager; + let mockSegmentManager: OdpSegmentManager; + + let eventApiManager: OdpEventApiManager; + let eventManager: OdpEventManager; + let segmentApiManager: OdpSegmentApiManager; + let segmentManager: OdpSegmentManager; + + beforeAll(() => { + mockLogger = mock(); + mockRequestHandler = mock(); + + odpConfig = new OdpConfig(); + logger = instance(mockLogger); + requestHandler = instance(mockRequestHandler); + + mockEventApiManager = mock(); + mockEventManager = mock(); + mockSegmentApiManager = mock(); + mockSegmentManager = mock(); + + eventApiManager = instance(mockEventApiManager); + eventManager = instance(mockEventManager); + segmentApiManager = instance(mockSegmentApiManager); + segmentManager = instance(mockSegmentManager); + }); + + beforeEach(() => { + resetCalls(mockLogger); + resetCalls(mockRequestHandler); + resetCalls(mockEventApiManager); + resetCalls(mockEventManager); + resetCalls(mockSegmentManager); + }); + + const browserOdpManagerInstance = () => + new BrowserOdpManager({ + disable: false, + eventManager, + segmentManager, + }); + + it('should register VUID automatically on BrowserOdpManager initialization', async () => { + const browserOdpManager = browserOdpManagerInstance(); + const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); + expect(browserOdpManager.vuid).toBe(vuidManager.vuid); + }); + + it('should drop relevant calls when OdpManager is initialized with the disabled flag, except for VUID', async () => { + const browserOdpManager = new BrowserOdpManager({ disable: true, logger }); + + verify(mockLogger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + + browserOdpManager.updateSettings(new OdpConfig('valid', 'host', [])); + expect(browserOdpManager.odpConfig).toBeUndefined; + + await browserOdpManager.fetchQualifiedSegments('vuid_user1', []); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + + browserOdpManager.identifyUser('vuid_user1'); + verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); + + expect(browserOdpManager.eventManager).toBeUndefined; + expect(browserOdpManager.segmentManager).toBeUndefined; + + const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); + expect(vuidManager.vuid.slice(0, 5)).toBe('vuid_'); + }); + + it('should start ODP Event Manager when ODP Manager is initialized', () => { + const browserOdpManager = browserOdpManagerInstance(); + verify(mockEventManager.start()).once(); + expect(browserOdpManager.eventManager).not.toBeUndefined(); + }); + + it('should stop ODP Event Manager when close is called', () => { + const browserOdpManager = browserOdpManagerInstance(); + verify(mockEventManager.stop()).never(); + + browserOdpManager.close(); + verify(mockEventManager.stop()).once(); + }); + + it('should use new settings in event manager when ODP Config is updated', async () => { + const browserOdpManager = new BrowserOdpManager({ + disable: false, + eventManager, + }); + + expect(browserOdpManager.eventManager).toBeDefined(); + verify(mockEventManager.updateSettings(anything())).once(); + verify(mockEventManager.start()).once(); + + await new Promise(resolve => setTimeout(resolve, 200)); // Wait for VuidManager to fetch from cache. + + verify(mockEventManager.registerVuid(anything())).once(); + + const didUpdateA = browserOdpManager.updateSettings(odpConfigA); + expect(didUpdateA).toBe(true); + expect(browserOdpManager.odpConfig.equals(odpConfigA)).toBe(true); + + const updateSettingsArgsA = capture(mockEventManager.updateSettings).last(); + expect(updateSettingsArgsA[0]).toStrictEqual(odpConfigA); + + browserOdpManager.identifyUser(userA); + const identifyUserArgsA = capture(mockEventManager.identifyUser).last(); + expect(identifyUserArgsA[0]).toStrictEqual(userA); + + const didUpdateB = browserOdpManager.updateSettings(odpConfigB); + expect(didUpdateB).toBe(true); + expect(browserOdpManager.odpConfig.equals(odpConfigB)).toBe(true); + + const updateSettingsArgsB = capture(mockEventManager.updateSettings).last(); + expect(updateSettingsArgsB[0]).toStrictEqual(odpConfigB); + + browserOdpManager.eventManager!.identifyUser(userB); + const identifyUserArgsB = capture(mockEventManager.identifyUser).last(); + expect(identifyUserArgsB[0]).toStrictEqual(userB); + }); + + it('should use new settings in segment manager when ODP Config is updated', () => { + const browserOdpManager = new BrowserOdpManager({ + disable: false, + segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), + }); + + const didUpdateA = browserOdpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); + expect(didUpdateA).toBe(true); + + browserOdpManager.fetchQualifiedSegments(vuidA); + const fetchQualifiedSegmentsArgsA = capture(mockSegmentApiManager.fetchSegments).last(); + expect(fetchQualifiedSegmentsArgsA).toStrictEqual([keyA, hostA, ODP_USER_KEY.VUID, vuidA, segmentsA]); + + const didUpdateB = browserOdpManager.updateSettings(new OdpConfig(keyB, hostB, segmentsB)); + expect(didUpdateB).toBe(true); + + browserOdpManager.fetchQualifiedSegments(vuidB); + + const fetchQualifiedSegmentsArgsB = capture(mockSegmentApiManager.fetchSegments).last(); + expect(fetchQualifiedSegmentsArgsB).toStrictEqual([keyB, hostB, ODP_USER_KEY.VUID, vuidB, segmentsB]); + }); + + it('should get event manager', () => { + const browserOdpManagerA = browserOdpManagerInstance(); + expect(browserOdpManagerA.eventManager).not.toBe(null); + + const browserOdpManagerB = new BrowserOdpManager({ + disable: false, + }); + expect(browserOdpManagerB.eventManager).not.toBe(null); + }); + + it('should get segment manager', () => { + const browserOdpManagerA = browserOdpManagerInstance(); + expect(browserOdpManagerA.segmentManager).not.toBe(null); + + const browserOdpManagerB = new BrowserOdpManager({ + disable: false, + }); + expect(browserOdpManagerB.eventManager).not.toBe(null); + }); +}); diff --git a/packages/optimizely-sdk/tests/odpManager.spec.ts b/packages/optimizely-sdk/tests/odpManager.spec.ts new file mode 100644 index 000000000..29b7e01e7 --- /dev/null +++ b/packages/optimizely-sdk/tests/odpManager.spec.ts @@ -0,0 +1,207 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/// + +import { anything, instance, mock, resetCalls, verify } from 'ts-mockito'; + +import { LOG_MESSAGES } from './../lib/utils/enums/index'; +import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; + +import { LogHandler, LogLevel } from '../lib/modules/logging'; +import { RequestHandler } from '../lib/utils/http_request_handler/http'; +import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; + +import { OdpManager } from './../lib/core/odp/odp_manager'; +import { OdpConfig } from '../lib/core/odp/odp_config'; +import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; +import { OdpEventManager } from '../lib/core/odp/odp_event_manager'; +import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; +import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; + +const keyA = 'key-a'; +const hostA = 'host-a'; +const segmentsA = ['a']; +const userA = 'fs-user-a'; + +const keyB = 'key-b'; +const hostB = 'host-b'; +const segmentsB = ['b']; +const userB = 'fs-user-b'; + +describe('OdpManager', () => { + let mockLogger: LogHandler; + let mockRequestHandler: RequestHandler; + + let odpConfig: OdpConfig; + let logger: LogHandler; + let requestHandler: RequestHandler; + + let mockEventApiManager: OdpEventApiManager; + let mockEventManager: OdpEventManager; + let mockSegmentApiManager: OdpSegmentApiManager; + let mockSegmentManager: OdpSegmentManager; + + let eventApiManager: OdpEventApiManager; + let eventManager: OdpEventManager; + let segmentApiManager: OdpSegmentApiManager; + let segmentManager: OdpSegmentManager; + + beforeAll(() => { + mockLogger = mock(); + mockRequestHandler = mock(); + + odpConfig = new OdpConfig(); + logger = instance(mockLogger); + requestHandler = instance(mockRequestHandler); + + mockEventApiManager = mock(); + mockEventManager = mock(); + mockSegmentApiManager = mock(); + mockSegmentManager = mock(); + + eventApiManager = instance(mockEventApiManager); + eventManager = instance(mockEventManager); + segmentApiManager = instance(mockSegmentApiManager); + segmentManager = instance(mockSegmentManager); + }); + + beforeEach(() => { + resetCalls(mockLogger); + resetCalls(mockRequestHandler); + resetCalls(mockEventApiManager); + resetCalls(mockEventManager); + resetCalls(mockSegmentManager); + }); + + const odpManagerInstance = (config?: OdpConfig) => + new OdpManager({ + disable: false, + requestHandler, + eventManager, + segmentManager, + }); + + it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { + const odpManager = new OdpManager({ disable: true, requestHandler, logger }); + verify(mockLogger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + + odpManager.updateSettings(new OdpConfig('valid', 'host', [])); + expect(odpManager.odpConfig).toBeUndefined; + + await odpManager.fetchQualifiedSegments('user1', []); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + + odpManager.identifyUser('user1'); + verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); + + expect(odpManager.eventManager).toBeUndefined; + expect(odpManager.segmentManager).toBeUndefined; + }); + + it('should start ODP Event Manager when ODP Manager is initialized', () => { + const odpManager = odpManagerInstance(); + verify(mockEventManager.start()).once(); + expect(odpManager.eventManager).not.toBeUndefined(); + }); + + it('should stop ODP Event Manager when close is called', () => { + const odpManager = odpManagerInstance(); + verify(mockEventManager.stop()).never(); + + odpManager.close(); + verify(mockEventManager.stop()).once(); + }); + + it('should use new settings in event manager when ODP Config is updated', async () => { + const odpManager = new OdpManager({ + disable: false, + requestHandler, + eventManager: new OdpEventManager({ + odpConfig, + apiManager: eventApiManager, + logger, + clientEngine: '', + clientVersion: '', + batchSize: 1, + flushInterval: 250, + }), + }); + + odpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); + + expect(odpManager.odpConfig.apiKey).toBe(keyA); + expect(odpManager.odpConfig.apiHost).toBe(hostA); + + // odpManager.identifyUser(userA); + + // verify(mockEventApiManager.sendEvents(keyA, hostA, anything())).once(); + + odpManager.updateSettings(new OdpConfig(keyB, hostB, segmentsB)); + expect(odpManager.odpConfig.apiKey).toBe(keyB); + expect(odpManager.odpConfig.apiHost).toBe(hostB); + + // odpManager.identifyUser(userB); + + // verify(mockEventApiManager.sendEvents(keyB, hostB, anything())).once(); + }); + + it('should use new settings in segment manager when ODP Config is updated', async () => { + const odpManager = new OdpManager({ + disable: false, + requestHandler, + segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), + }); + + odpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); + + expect(odpManager.odpConfig.apiKey).toBe(keyA); + expect(odpManager.odpConfig.apiHost).toBe(hostA); + odpManager.fetchQualifiedSegments(userA); + + await new Promise(resolve => setTimeout(resolve, 400)); + verify(mockSegmentApiManager.fetchSegments(keyA, hostA, ODP_USER_KEY.FS_USER_ID, userA, anything())).once(); + + odpManager.updateSettings(new OdpConfig(keyB, hostB, segmentsB)); + expect(odpManager.odpConfig.apiKey).toBe(keyB); + expect(odpManager.odpConfig.apiHost).toBe(hostB); + odpManager.fetchQualifiedSegments(userB); + + await new Promise(resolve => setTimeout(resolve, 400)); + verify(mockSegmentApiManager.fetchSegments(keyB, hostB, ODP_USER_KEY.FS_USER_ID, userB, anything())).once(); + }); + + it('should get event manager', () => { + const odpManagerA = odpManagerInstance(); + expect(odpManagerA.eventManager).not.toBe(null); + + const odpManagerB = new OdpManager({ + disable: false, + requestHandler, + }); + expect(odpManagerB.eventManager).not.toBe(null); + }); + + it('should get segment manager', () => { + const odpManagerA = odpManagerInstance(); + expect(odpManagerA.segmentManager).not.toBe(null); + + const odpManagerB = new OdpManager({ + disable: false, + requestHandler, + }); + expect(odpManagerB.eventManager).not.toBe(null); + }); +}); diff --git a/packages/optimizely-sdk/tests/odpSegmentApiManager.ts b/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts similarity index 98% rename from packages/optimizely-sdk/tests/odpSegmentApiManager.ts rename to packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts index 5056145a2..d59f6fb69 100644 --- a/packages/optimizely-sdk/tests/odpSegmentApiManager.ts +++ b/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -150,7 +150,7 @@ describe('OdpSegmentApiManager', () => { const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - expect(segments).toHaveLength(2); + expect(segments?.length).toEqual(2); expect(segments).toContain('has_email'); expect(segments).toContain('has_email_opted_in'); verify(mockLogger.log(anything(), anyString())).never(); @@ -161,7 +161,7 @@ describe('OdpSegmentApiManager', () => { const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, ODP_USER_KEY.FS_USER_ID, USER_VALUE, []); - expect(segments).toHaveLength(0); + expect(segments?.length).toEqual(0); verify(mockLogger.log(anything(), anyString())).never(); }); @@ -174,7 +174,7 @@ describe('OdpSegmentApiManager', () => { const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - expect(segments).toHaveLength(0); + expect(segments?.length).toEqual(0); verify(mockLogger.log(anything(), anyString())).never(); }); diff --git a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts index 91277c5ee..9fb465dca 100644 --- a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,9 @@ describe('OdpSegmentManager', () => { const userKey: ODP_USER_KEY = ODP_USER_KEY.VUID; const userValue = 'test-user'; + const validTestOdpConfig = new OdpConfig('valid-key', 'host', ['new-customer']); + const invalidTestOdpConfig = new OdpConfig('invalid-key', 'host', ['new-customer']); + beforeEach(() => { resetCalls(mockLogHandler); resetCalls(mockRequestHandler); @@ -61,7 +64,7 @@ describe('OdpSegmentManager', () => { const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; odpConfig = new OdpConfig(API_KEY, API_HOST, []); - const segmentsCache = new LRUCache>({ + const segmentsCache = new LRUCache({ maxSize: 1000, timeout: 1000, }); @@ -70,7 +73,7 @@ describe('OdpSegmentManager', () => { }); it('should fetch segments successfully on cache miss.', async () => { - odpConfig.update('host', 'valid', ['new-customer']); + odpConfig.update(validTestOdpConfig); setCache(userKey, '123', ['a']); const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); @@ -78,7 +81,7 @@ describe('OdpSegmentManager', () => { }); it('should fetch segments successfully on cache hit.', async () => { - odpConfig.update('host', 'valid', ['new-customer']); + odpConfig.update(validTestOdpConfig); setCache(userKey, userValue, ['a']); const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); @@ -86,14 +89,14 @@ describe('OdpSegmentManager', () => { }); it('should throw an error when fetching segments returns an error.', async () => { - odpConfig.update('host', 'invalid-key', ['new-customer']); + odpConfig.update(invalidTestOdpConfig); const segments = await manager.fetchQualifiedSegments(userKey, userValue, []); expect(segments).toBeNull; }); it('should ignore the cache if the option is included in the options array.', async () => { - odpConfig.update('host', 'valid', ['new-customer']); + odpConfig.update(validTestOdpConfig); setCache(userKey, userValue, ['a']); options = [OptimizelySegmentOption.IGNORE_CACHE]; @@ -103,7 +106,7 @@ describe('OdpSegmentManager', () => { }); it('should reset the cache if the option is included in the options array.', async () => { - odpConfig.update('host', 'valid', ['new-customer']); + odpConfig.update(validTestOdpConfig); setCache(userKey, userValue, ['a']); setCache(userKey, '123', ['a']); setCache(userKey, '456', ['a']); @@ -121,7 +124,7 @@ describe('OdpSegmentManager', () => { // Utility Functions - function setCache(userKey: string, userValue: string, value: Array) { + function setCache(userKey: string, userValue: string, value: string[]) { const cacheKey = manager.makeCacheKey(userKey, userValue); manager.segmentsCache.save({ key: cacheKey, @@ -129,7 +132,7 @@ describe('OdpSegmentManager', () => { }); } - function peekCache(userKey: string, userValue: string): Array | null { + function peekCache(userKey: string, userValue: string): string[] | null { const cacheKey = manager.makeCacheKey(userKey, userValue); return manager.segmentsCache.peek(cacheKey); } diff --git a/packages/optimizely-sdk/tests/vuidManager.spec.ts b/packages/optimizely-sdk/tests/vuidManager.spec.ts index 9fe746f0a..12ee6cff9 100644 --- a/packages/optimizely-sdk/tests/vuidManager.spec.ts +++ b/packages/optimizely-sdk/tests/vuidManager.spec.ts @@ -31,11 +31,11 @@ describe('VuidManager', () => { when(mockCache.set(anyString(), anything())).thenResolve(); VuidManager.instance(instance(mockCache)); }); - - beforeEach(()=>{ + + beforeEach(() => { resetCalls(mockCache); VuidManager['_reset'](); - }) + }); it('should make a VUID', async () => { const manager = await VuidManager.instance(instance(mockCache)); @@ -50,9 +50,9 @@ describe('VuidManager', () => { it('should test if a VUID is valid', async () => { const manager = await VuidManager.instance(instance(mockCache)); - expect(manager['isVuid']('vuid_123')).toBe(true); - expect(manager['isVuid']('vuid-123')).toBe(false); - expect(manager['isVuid']('123')).toBe(false); + expect(VuidManager.isVuid('vuid_123')).toBe(true); + expect(VuidManager.isVuid('vuid-123')).toBe(false); + expect(VuidManager.isVuid('123')).toBe(false); }); it('should auto-save and auto-load', async () => { @@ -67,8 +67,8 @@ describe('VuidManager', () => { const vuid2 = manager2.vuid; expect(vuid1).toStrictEqual(vuid2); - expect(manager2['isVuid'](vuid1)).toBe(true); - expect(manager1['isVuid'](vuid2)).toBe(true); + expect(VuidManager.isVuid(vuid1)).toBe(true); + expect(VuidManager.isVuid(vuid2)).toBe(true); await cache.remove('optimizely-odp'); @@ -77,7 +77,7 @@ describe('VuidManager', () => { const vuid3 = manager2.vuid; expect(vuid3).not.toStrictEqual(vuid1); - expect(manager2['isVuid'](vuid3)).toBe(true); + expect(VuidManager.isVuid(vuid3)).toBe(true); }); it('should handle no valid optimizely-vuid in the cache', async () => { @@ -87,7 +87,7 @@ describe('VuidManager', () => { verify(mockCache.get(anyString())).once(); verify(mockCache.set(anyString(), anything())).once(); - expect(manager['isVuid'](manager.vuid)).toBe(true); + expect(VuidManager.isVuid(manager.vuid)).toBe(true); }); it('should create a new vuid if old VUID from cache is not valid', async () => { @@ -97,7 +97,6 @@ describe('VuidManager', () => { verify(mockCache.get(anyString())).once(); verify(mockCache.set(anyString(), anything())).once(); - expect(manager['isVuid'](manager.vuid)).toBe(true); + expect(VuidManager.isVuid(manager.vuid)).toBe(true); }); }); - diff --git a/packages/optimizely-sdk/tsconfig.json b/packages/optimizely-sdk/tsconfig.json index 51dfbda08..5e19876a5 100644 --- a/packages/optimizely-sdk/tsconfig.json +++ b/packages/optimizely-sdk/tsconfig.json @@ -14,6 +14,7 @@ "outDir": "./dist", "sourceMap": true, "skipLibCheck": true, + "useUnknownInCatchVariables": false }, "exclude": [ "./dist", From 7808c73be2161feba43b5ea636331a4e21113c4b Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Thu, 9 Mar 2023 14:55:01 -0500 Subject: [PATCH 003/200] [FSSDK-8475] ODP Integration for Optimizely Client and User Context (Browser) (#799) --- .gitignore | 3 + packages/optimizely-sdk/jest.config.js | 3 +- .../lib/core/decision_service/index.tests.js | 39 + .../lib/core/odp/odp_event_manager.ts | 36 +- .../lib/core/odp/odp_manager.ts | 8 +- .../lib/core/odp/odp_segment_api_manager.ts | 6 +- packages/optimizely-sdk/lib/export_types.ts | 6 +- .../optimizely-sdk/lib/index.browser.tests.js | 413 +++++- packages/optimizely-sdk/lib/index.browser.ts | 43 +- .../lib/optimizely/index.browser.ts | 62 + .../lib/optimizely/index.tests.js | 1165 +++++++++-------- .../optimizely-sdk/lib/optimizely/index.ts | 533 ++++---- .../optimizely_user_context/index.tests.js | 245 ++-- .../lib/optimizely_user_context/index.ts | 77 +- .../browserAsyncStorageCache.ts | 55 +- .../lib/plugins/odp_manager/index.browser.ts | 29 +- .../lib/plugins/vuid_manager/index.ts | 2 +- packages/optimizely-sdk/lib/shared_types.ts | 32 +- .../optimizely-sdk/lib/utils/enums/index.ts | 15 +- .../lib/utils/json_schema_validator/index.ts | 8 +- .../utils/local_storage/tryLocalStorage.ts | 32 + .../lib/utils/lru_cache/browser_lru_cache.ts | 11 +- .../lib/utils/lru_cache/lru_cache.ts | 13 +- .../lib/utils/lru_cache/server_lru_cache.ts | 8 +- packages/optimizely-sdk/rollup.config.js | 34 +- .../tests/odpEventManager.spec.ts | 6 +- .../tests/odpManager.browser.spec.ts | 79 +- .../optimizely-sdk/tests/odpManager.spec.ts | 2 +- .../tests/odpSegmentApiManager.spec.ts | 2 +- 29 files changed, 1822 insertions(+), 1145 deletions(-) create mode 100644 packages/optimizely-sdk/lib/optimizely/index.browser.ts create mode 100644 packages/optimizely-sdk/lib/utils/local_storage/tryLocalStorage.ts diff --git a/.gitignore b/.gitignore index 702b61d6d..ad0e62dca 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ dist/ # user-specific ignores ought to be defined in user's `core.excludesfile` .idea/* .DS_STORE + +browserstack.err +local.log \ No newline at end of file diff --git a/packages/optimizely-sdk/jest.config.js b/packages/optimizely-sdk/jest.config.js index 64694672c..d8ce6a217 100644 --- a/packages/optimizely-sdk/jest.config.js +++ b/packages/optimizely-sdk/jest.config.js @@ -8,7 +8,8 @@ module.exports = { "uuid": require.resolve('uuid'), }, "testPathIgnorePatterns" : [ - "tests/testUtils.ts" + "tests/testUtils.ts", + "dist" ], "moduleFileExtensions": [ "ts", diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js index 5f624a12f..ba0dd5fbb 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js @@ -68,6 +68,7 @@ describe('lib/core/decision_service', function() { describe('#getVariation', function() { it('should return the correct variation for the given experiment key and user ID for a running experiment', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'tester' }); @@ -86,6 +87,7 @@ describe('lib/core/decision_service', function() { it('should return the whitelisted variation if the user is whitelisted', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user2' }); @@ -108,6 +110,7 @@ describe('lib/core/decision_service', function() { it('should return null if the user does not meet audience conditions', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user3' }); @@ -136,6 +139,7 @@ describe('lib/core/decision_service', function() { it('should return null if the experiment is not running', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1' }); @@ -165,6 +169,7 @@ describe('lib/core/decision_service', function() { }, }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -222,6 +227,7 @@ describe('lib/core/decision_service', function() { }); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -250,6 +256,7 @@ describe('lib/core/decision_service', function() { }); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -276,6 +283,7 @@ describe('lib/core/decision_service', function() { userProfileLookupStub.returns(null); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -308,6 +316,7 @@ describe('lib/core/decision_service', function() { }); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -343,6 +352,7 @@ describe('lib/core/decision_service', function() { experiment_bucket_map: {}, // no decisions for user }); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -378,6 +388,7 @@ describe('lib/core/decision_service', function() { userProfileLookupStub.throws(new Error('I am an error')); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -403,6 +414,7 @@ describe('lib/core/decision_service', function() { userProfileSaveStub.throws(new Error('I am an error')); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -456,6 +468,7 @@ describe('lib/core/decision_service', function() { experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -500,6 +513,7 @@ describe('lib/core/decision_service', function() { }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -544,6 +558,7 @@ describe('lib/core/decision_service', function() { }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -579,6 +594,7 @@ describe('lib/core/decision_service', function() { }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -1146,6 +1162,7 @@ describe('lib/core/decision_service', function() { }, }); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'test_user', attributes: userAttributesWithBucketingId, @@ -1248,6 +1265,7 @@ describe('lib/core/decision_service', function() { var experiment; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { @@ -1489,6 +1507,7 @@ describe('lib/core/decision_service', function() { var getVariationStub; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); @@ -1523,6 +1542,7 @@ describe('lib/core/decision_service', function() { var user; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); @@ -1583,6 +1603,7 @@ describe('lib/core/decision_service', function() { var user; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); @@ -1642,6 +1663,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation and experiment from the audience targeting rule', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { test_attribute: 'test_value' }, @@ -1778,6 +1800,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation and experiment from the everyone else targeting rule', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: {}, @@ -1913,6 +1936,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with no variation, no experiment and source rollout', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); @@ -1960,6 +1984,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation and experiment from the everyone else targeting rule', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { test_attribute: 'test_value' } @@ -2109,6 +2134,7 @@ describe('lib/core/decision_service', function() { // No attributes passed to the user context, so user is not in the audience for the experiment // It should fall through to the rollout user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1' }); @@ -2203,6 +2229,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with no variation, no experiment and source rollout', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1' }); @@ -2239,6 +2266,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket less than 2500', function() { generateBucketValueStub.returns(2400); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2268,6 +2296,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 2500 to 5000', function() { generateBucketValueStub.returns(4000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2297,6 +2326,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 5000 to 7500', function() { generateBucketValueStub.returns(6500); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2326,6 +2356,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation and source rollout in mutex group bucket greater than 7500', function() { generateBucketValueStub.returns(8000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2373,6 +2404,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation for rollout in mutex group with audience mismatch', function() { generateBucketValueStub.returns(2400); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment_invalid' } @@ -2429,6 +2461,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket less than 2500', function() { generateBucketValueStub.returns(2400); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2459,6 +2492,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 2500 to 5000', function() { generateBucketValueStub.returns(4000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2489,6 +2523,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 5000 to 7500', function() { generateBucketValueStub.returns(6500); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2519,6 +2554,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation and source rollout in mutex group bucket greater than 7500', function() { generateBucketValueStub.returns(8000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2566,6 +2602,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation for rollout in mutex group bucket range 2500 to 5000', function() { generateBucketValueStub.returns(4000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment_invalid' } @@ -2634,6 +2671,7 @@ describe('lib/core/decision_service', function() { it('should call buildBucketerParams with user Id when bucketing Id is not provided in the attributes', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'testUser', attributes: { test_attribute: 'test_value' } @@ -2651,6 +2689,7 @@ describe('lib/core/decision_service', function() { $opt_bucketing_id: 'abcdefg', }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'testUser', attributes, diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 7eda72a3b..d372dac66 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -17,7 +17,7 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { uuid } from '../../utils/fns'; -import { ERROR_MESSAGES, ODP_USER_KEY, ODP_EVENT_TYPE } from '../../utils/enums'; +import { ERROR_MESSAGES, ODP_USER_KEY, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from '../../utils/enums'; import { OdpEvent } from './odp_event'; import { OdpConfig } from './odp_config'; @@ -140,7 +140,17 @@ export class OdpEventManager implements IOdpEventManager { this.clientEngine = clientEngine; this.clientVersion = clientVersion; - this.queueSize = queueSize || (process ? DEFAULT_SERVER_QUEUE_SIZE : DEFAULT_BROWSER_QUEUE_SIZE); + let defaultQueueSize = DEFAULT_BROWSER_QUEUE_SIZE; + + try { + if (process) { + defaultQueueSize = DEFAULT_SERVER_QUEUE_SIZE; + } + } catch (e) { + // TODO: Create Browser and Non-Browser specific variants of ODP Event Manager to avoid this try/catch + } + + this.queueSize = queueSize || defaultQueueSize; this.batchSize = batchSize || DEFAULT_BATCH_SIZE; this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; @@ -191,7 +201,7 @@ export class OdpEventManager implements IOdpEventManager { const identifiers = new Map(); identifiers.set(ODP_USER_KEY.VUID, vuid); - const event = new OdpEvent(ODP_EVENT_TYPE, 'client_initialized', identifiers); + const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, identifiers); this.sendEvent(event); } @@ -215,7 +225,7 @@ export class OdpEventManager implements IOdpEventManager { identifiers.set(ODP_USER_KEY.FS_USER_ID, userId); } - const event = new OdpEvent(ODP_EVENT_TYPE, 'identified', identifiers); + const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.IDENTIFIED, identifiers); this.sendEvent(event); } @@ -381,14 +391,20 @@ export class OdpEventManager implements IOdpEventManager { return true; } - if (process) { - // if Node/server-side context, empty queue items before ready state - this.logger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); - this.queue = new Array(); - } else { - // in Browser/client-side context, give debug message but leave events in queue + try { + if (process) { + // if Node/server-side context, empty queue items before ready state + this.logger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); + this.queue = new Array(); + } else { + // in Browser/client-side context, give debug message but leave events in queue + this.logger.log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); + } + } catch (e) { + // TODO: Create Browser and Non-Browser specific variants of ODP Event Manager to avoid this try/catch this.logger.log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); } + return false; } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts index b77a8de74..00d413aa6 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts @@ -88,7 +88,7 @@ export class OdpManager { this.logger = logger || getLogger(); if (!this.enabled) { - this.logger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_ENABLED); + this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); return; } @@ -181,7 +181,8 @@ export class OdpManager { } if (!this.segmentManager) { - throw new Error(ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING); + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING); + return null; } if (VuidManager.isVuid(userId)) { @@ -209,7 +210,8 @@ export class OdpManager { } if (!this.eventManager) { - throw new Error(ERROR_MESSAGES.ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING); + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING); + return; } if (userId && VuidManager.isVuid(userId)) { diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts index aa21b96b5..fd7221717 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts @@ -130,14 +130,14 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { */ private toGraphQLJson = (userKey: string, userValue: string, segmentsToCheck: string[]): string => [ - '{"query" : "query {customer"', - `(${userKey} : "${userValue}") `, + '{"query" : "query {customer', + `(${userKey} : \\"${userValue}\\") `, '{audiences', '(subset: [', ...(segmentsToCheck?.map( (segment, index) => `\\"${segment}\\"${index < segmentsToCheck.length - 1 ? ',' : ''}` ) || ''), - '] {edges {node {name state}}}}}"}', + ']) {edges {node {name state}}}}}"}', ].join(''); /** diff --git a/packages/optimizely-sdk/lib/export_types.ts b/packages/optimizely-sdk/lib/export_types.ts index 17d307ae8..46e89c321 100644 --- a/packages/optimizely-sdk/lib/export_types.ts +++ b/packages/optimizely-sdk/lib/export_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,5 +37,5 @@ export { Client, ActivateListenerPayload, TrackListenerPayload, - NotificationCenter -} from './shared_types' + NotificationCenter, +} from './shared_types'; diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index c2417b46b..a4d4b71ba 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import logging from './modules/logging/logger'; +import logging, { getLogger } from './modules/logging/logger'; import { assert } from 'chai'; import sinon from 'sinon'; @@ -24,23 +24,61 @@ import packageJSON from '../package.json'; import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; +import OptimizelyUserContext from './optimizely_user_context'; +import { LOG_MESSAGES, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from './utils/enums'; +import { BrowserLRUCache } from './utils/lru_cache'; +import { OdpConfig } from './core/odp/odp_config'; +import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; +import { OdpEvent } from './core/odp/odp_event'; var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; -describe('javascript-sdk', function () { +class MockLocalStorage { + store = {}; + + constructor() {} + + getItem(key) { + return this.store[key]; + } + + setItem(key, value) { + this.store[key] = value.toString(); + } + + clear() { + this.store = {}; + } + + removeItem(key) { + delete this.store[key]; + } +} + +if (!global.window) { + try { + global.window = { + localStorage: new MockLocalStorage(), + }; + } catch (e) { + console.error('Unable to overwrite global.window.'); + } +} + +describe('javascript-sdk (Browser)', function() { var clock; - beforeEach(function () { + beforeEach(function() { sinon.stub(optimizelyFactory.eventDispatcher, 'dispatchEvent'); clock = sinon.useFakeTimers(new Date()); }); - afterEach(function () { + afterEach(function() { optimizelyFactory.eventDispatcher.dispatchEvent.restore(); clock.restore(); }); - describe('APIs', function () { - it('should expose logger, errorHandler, eventDispatcher and enums', function () { + describe('APIs', function() { + it('should expose logger, errorHandler, eventDispatcher and enums', function() { assert.isDefined(optimizelyFactory.logging); assert.isDefined(optimizelyFactory.logging.createLogger); assert.isDefined(optimizelyFactory.logging.createNoOpLogger); @@ -49,12 +87,12 @@ describe('javascript-sdk', function () { assert.isDefined(optimizelyFactory.enums); }); - describe('createInstance', function () { - var fakeErrorHandler = { handleError: function () { } }; - var fakeEventDispatcher = { dispatchEvent: function () { } }; + describe('createInstance', function() { + var fakeErrorHandler = { handleError: function() {} }; + var fakeEventDispatcher = { dispatchEvent: function() {} }; var silentLogger; - beforeEach(function () { + beforeEach(function() { silentLogger = optimizelyFactory.logging.createLogger({ logLevel: optimizelyFactory.enums.LOG_LEVEL.INFO, logToConsole: false, @@ -67,30 +105,30 @@ describe('javascript-sdk', function () { sinon.stub(LocalStoragePendingEventsDispatcher.prototype, 'sendPendingEvents'); }); - afterEach(function () { + afterEach(function() { LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents.restore(); optimizelyFactory.__internalResetRetryState(); console.error.restore(); configValidator.validate.restore(); - delete global.XMLHttpRequest + delete global.XMLHttpRequest; }); - describe('when an eventDispatcher is not passed in', function () { - it('should wrap the default eventDispatcher and invoke sendPendingEvents', function () { + describe('when an eventDispatcher is not passed in', function() { + it('should wrap the default eventDispatcher and invoke sendPendingEvents', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, logger: silentLogger, }); // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); }); }); - describe('when an eventDispatcher is passed in', function () { - it('should NOT wrap the default eventDispatcher and invoke sendPendingEvents', function () { + describe('when an eventDispatcher is passed in', function() { + it('should NOT wrap the default eventDispatcher and invoke sendPendingEvents', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, @@ -98,20 +136,20 @@ describe('javascript-sdk', function () { logger: silentLogger, }); // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); sinon.assert.notCalled(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); }); }); - it('should invoke resendPendingEvents at most once', function () { + it('should invoke resendPendingEvents at most once', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, logger: silentLogger, }); // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); @@ -120,24 +158,24 @@ describe('javascript-sdk', function () { errorHandler: fakeErrorHandler, logger: silentLogger, }); - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); }); - it('should not throw if the provided config is not valid', function () { + it('should not throw if the provided config is not valid', function() { configValidator.validate.throws(new Error('Invalid config or something')); - assert.doesNotThrow(function () { + assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, logger: silentLogger, }); // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); }); }); - it('should create an instance of optimizely', function () { + it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, @@ -145,13 +183,13 @@ describe('javascript-sdk', function () { logger: silentLogger, }); // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); assert.equal(optlyInstance.clientVersion, '4.9.2'); }); - it('should set the JavaScript client engine and version', function () { + it('should set the JavaScript client engine and version', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, @@ -159,12 +197,12 @@ describe('javascript-sdk', function () { logger: silentLogger, }); // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); assert.equal('javascript-sdk', optlyInstance.clientEngine); assert.equal(packageJSON.version, optlyInstance.clientVersion); }); - it('should allow passing of "react-sdk" as the clientEngine', function () { + it('should allow passing of "react-sdk" as the clientEngine', function() { var optlyInstance = optimizelyFactory.createInstance({ clientEngine: 'react-sdk', datafile: {}, @@ -173,11 +211,11 @@ describe('javascript-sdk', function () { logger: silentLogger, }); // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); assert.equal('react-sdk', optlyInstance.clientEngine); }); - it('should activate with provided event dispatcher', function () { + it('should activate with provided event dispatcher', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -188,7 +226,7 @@ describe('javascript-sdk', function () { assert.strictEqual(activate, 'control'); }); - it('should be able to set and get a forced variation', function () { + it('should be able to set and get a forced variation', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -203,7 +241,7 @@ describe('javascript-sdk', function () { assert.strictEqual(variation, 'control'); }); - it('should be able to set and unset a forced variation', function () { + it('should be able to set and unset a forced variation', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -224,7 +262,7 @@ describe('javascript-sdk', function () { assert.strictEqual(variation2, null); }); - it('should be able to set multiple experiments for one user', function () { + it('should be able to set multiple experiments for one user', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -249,7 +287,7 @@ describe('javascript-sdk', function () { assert.strictEqual(variation2, 'controlLaunched'); }); - it('should be able to set multiple experiments for one user, and unset one', function () { + it('should be able to set multiple experiments for one user, and unset one', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -277,7 +315,7 @@ describe('javascript-sdk', function () { assert.strictEqual(variation2, null); }); - it('should be able to set multiple experiments for one user, and reset one', function () { + it('should be able to set multiple experiments for one user, and reset one', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -309,7 +347,7 @@ describe('javascript-sdk', function () { assert.strictEqual(variation2, 'variationLaunched'); }); - it('should override bucketing when setForcedVariation is called', function () { + it('should override bucketing when setForcedVariation is called', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -330,7 +368,7 @@ describe('javascript-sdk', function () { assert.strictEqual(variation, 'variation'); }); - it('should override bucketing when setForcedVariation is called for a not running experiment', function () { + it('should override bucketing when setForcedVariation is called for a not running experiment', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), errorHandler: fakeErrorHandler, @@ -349,16 +387,16 @@ describe('javascript-sdk', function () { assert.strictEqual(variation, null); }); - describe('when passing in logLevel', function () { - beforeEach(function () { + describe('when passing in logLevel', function() { + beforeEach(function() { sinon.stub(logging, 'setLogLevel'); }); - afterEach(function () { + afterEach(function() { logging.setLogLevel.restore(); }); - it('should call logging.setLogLevel', function () { + it('should call logging.setLogLevel', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, @@ -368,17 +406,17 @@ describe('javascript-sdk', function () { }); }); - describe('when passing in logger', function () { - beforeEach(function () { + describe('when passing in logger', function() { + beforeEach(function() { sinon.stub(logging, 'setLogHandler'); }); - afterEach(function () { + afterEach(function() { logging.setLogHandler.restore(); }); - it('should call logging.setLogHandler with the supplied logger', function () { - var fakeLogger = { log: function () { } }; + it('should call logging.setLogHandler with the supplied logger', function() { + var fakeLogger = { log: function() {} }; optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), logger: fakeLogger, @@ -388,16 +426,16 @@ describe('javascript-sdk', function () { }); }); - describe('event processor configuration', function () { - beforeEach(function () { + describe('event processor configuration', function() { + beforeEach(function() { sinon.stub(eventProcessor, 'createEventProcessor'); }); - afterEach(function () { + afterEach(function() { eventProcessor.createEventProcessor.restore(); }); - it('should use default event flush interval when none is provided', function () { + it('should use default event flush interval when none is provided', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -412,16 +450,16 @@ describe('javascript-sdk', function () { ); }); - describe('with an invalid flush interval', function () { - beforeEach(function () { + describe('with an invalid flush interval', function() { + beforeEach(function() { sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(false); }); - afterEach(function () { + afterEach(function() { eventProcessorConfigValidator.validateEventFlushInterval.restore(); }); - it('should ignore the event flush interval and use the default instead', function () { + it('should ignore the event flush interval and use the default instead', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -438,16 +476,16 @@ describe('javascript-sdk', function () { }); }); - describe('with a valid flush interval', function () { - beforeEach(function () { + describe('with a valid flush interval', function() { + beforeEach(function() { sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(true); }); - afterEach(function () { + afterEach(function() { eventProcessorConfigValidator.validateEventFlushInterval.restore(); }); - it('should use the provided event flush interval', function () { + it('should use the provided event flush interval', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -464,7 +502,7 @@ describe('javascript-sdk', function () { }); }); - it('should use default event batch size when none is provided', function () { + it('should use default event batch size when none is provided', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -479,16 +517,16 @@ describe('javascript-sdk', function () { ); }); - describe('with an invalid event batch size', function () { - beforeEach(function () { + describe('with an invalid event batch size', function() { + beforeEach(function() { sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(false); }); - afterEach(function () { + afterEach(function() { eventProcessorConfigValidator.validateEventBatchSize.restore(); }); - it('should ignore the event batch size and use the default instead', function () { + it('should ignore the event batch size and use the default instead', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -505,16 +543,16 @@ describe('javascript-sdk', function () { }); }); - describe('with a valid event batch size', function () { - beforeEach(function () { + describe('with a valid event batch size', function() { + beforeEach(function() { sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(true); }); - afterEach(function () { + afterEach(function() { eventProcessorConfigValidator.validateEventBatchSize.restore(); }); - it('should use the provided event batch size', function () { + it('should use the provided event batch size', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -532,5 +570,244 @@ describe('javascript-sdk', function () { }); }); }); + + describe('ODP/ATS', () => { + var sandbox = sinon.sandbox.create(); + + const fakeOptimizely = { + identifyUser: sinon.stub().returns(), + }; + + const fakeErrorHandler = { handleError: function() {} }; + const fakeEventDispatcher = { dispatchEvent: function() {} }; + let logger = getLogger(); + + const testFsUserId = 'fs_test_user'; + const testVuid = 'vuid_test_user'; + + beforeEach(function() { + sandbox.stub(logger, 'log'); + sandbox.stub(logger, 'error'); + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('should send identify event by default when initialized', () => { + new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId: testFsUserId, + }); + + sinon.assert.calledOnce(fakeOptimizely.identifyUser); + + sinon.assert.calledWith(fakeOptimizely.identifyUser, testFsUserId); + }); + + it('should log info when odp is disabled', () => { + const disabledClient = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: BrowserOdpManager.createBrowserOdpManager({ + logger, + odpOptions: { + disabled: true, + }, + }), + }); + + sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, LOG_MESSAGES.ODP_DISABLED); + }); + + it('should accept a valid custom cache size', () => { + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: BrowserOdpManager.createBrowserOdpManager({ + logger, + odpOptions: { + segmentsCacheSize: 10, + }, + }), + }); + + sinon.assert.calledWith( + logger.log, + optimizelyFactory.enums.LOG_LEVEL.DEBUG, + 'Provisioning cache with maxSize of 10' + ); + }); + + it('should accept a custom cache timeout', () => { + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: BrowserOdpManager.createBrowserOdpManager({ + logger, + odpOptions: { + segmentsCacheTimeout: 10, + }, + }), + }); + + sinon.assert.calledWith( + logger.log, + optimizelyFactory.enums.LOG_LEVEL.DEBUG, + 'Provisioning cache with timeout of 10' + ); + }); + + it('should accept both a custom cache size and timeout', () => { + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: BrowserOdpManager.createBrowserOdpManager({ + logger, + odpOptions: { + segmentsCacheSize: 10, + segmentsCacheTimeout: 10, + }, + }), + }); + + sinon.assert.calledWith( + logger.log, + optimizelyFactory.enums.LOG_LEVEL.DEBUG, + 'Provisioning cache with maxSize of 10' + ); + + sinon.assert.calledWith( + logger.log, + optimizelyFactory.enums.LOG_LEVEL.DEBUG, + 'Provisioning cache with timeout of 10' + ); + }); + + it('should accept a valid custom odp segment manager', () => { + const fakeSegmentManager = { + fetchQualifiedSegments: sinon.spy(), + updateSettings: sinon.spy(), + }; + + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: BrowserOdpManager.createBrowserOdpManager({ + logger, + odpOptions: { + segmentManager: fakeSegmentManager, + }, + }), + }); + + client.fetchQualifiedSegments(testVuid); + + sinon.assert.calledWith(fakeSegmentManager.updateSettings, new OdpConfig()); + }); + + it('should accept a valid custom odp event manager', () => { + const fakeEventManager = { + start: sinon.spy(), + updateSettings: sinon.spy(), + flush: sinon.spy(), + stop: sinon.spy(), + registerVuid: sinon.spy(), + identifyUser: sinon.spy(), + sendEvent: sinon.spy(), + }; + + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: BrowserOdpManager.createBrowserOdpManager({ + logger, + odpOptions: { + eventManager: fakeEventManager, + }, + }), + }); + + sinon.assert.called(fakeEventManager.start); + }); + + // TODO: Finish this test + it('should send an odp event with sendOdpEvent', async () => { + const fakeOdpManager = { + sendEvent: sinon.spy(), + updateSettings: sinon.spy(), + identifyUser: sinon.spy(), + close: sinon.spy(), + }; + + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: fakeOdpManager, + }); + + try { + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isEmpty(readyData.reason); + client.sendOdpEvent({ + action: ODP_EVENT_ACTION.INITIALIZED, + }); + + sinon.assert.notCalled(logger.error); + sinon.assert.called(fakeOdpManager.sendEvent); + } catch (e) {} + }); + + // TODO: Finish this test + it('should log an error when attempting to send an odp event when odp is disabled', async () => { + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: BrowserOdpManager.createBrowserOdpManager({ + logger, + odpOptions: { + disabled: true, + }, + }), + }); + + try { + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isEmpty(readyData.reason); + client.sendOdpEvent({ + action: ODP_EVENT_ACTION.INITIALIZED, + }); + + sinon.assert.calledWith(logger.error, 'ODP event send failed.'); + sinon.assert.calledWith(logger.error, 'ODP is not enabled.'); + } catch (e) {} + }); + }); }); }); diff --git a/packages/optimizely-sdk/lib/index.browser.ts b/packages/optimizely-sdk/lib/index.browser.ts index 1c8cf4068..7f6525c74 100644 --- a/packages/optimizely-sdk/lib/index.browser.ts +++ b/packages/optimizely-sdk/lib/index.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2019-2022 Optimizely + * Copyright 2016-2017, 2019-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,20 @@ * limitations under the License. */ import logHelper from './modules/logging/logger'; -import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, -} from './modules/logging'; +import { getLogger, setErrorHandler, getErrorHandler, LogLevel } from './modules/logging'; import { LocalStoragePendingEventsDispatcher } from './modules/event_processor'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './plugins/event_dispatcher/index.browser'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; -import Optimizely from './optimizely'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import { createNotificationCenter } from './core/notification_center'; import { default as eventProcessor } from './plugins/event_processor'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; +import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; import { createHttpPollingDatafileManager } from './plugins/datafile_manager/browser_http_polling_datafile_manager'; +import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; +import BrowserOptimizely from './optimizely/index.browser'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -53,7 +49,7 @@ let hasRetriedEvents = false; const createInstance = function(config: Config): Client | null { try { // TODO warn about setting per instance errorHandler / logger / logLevel - let isValidInstance = false + let isValidInstance = false; if (config.errorHandler) { setErrorHandler(config.errorHandler); @@ -71,7 +67,7 @@ const createInstance = function(config: Config): Client | null { configValidator.validate(config); isValidInstance = true; // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex: any) { + } catch (ex) { logger.error(ex); } @@ -114,22 +110,25 @@ const createInstance = function(config: Config): Client | null { dispatcher: eventDispatcher, flushInterval: eventFlushInterval, batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, + maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, notificationCenter, - } + }; - const optimizelyOptions = { + const optimizelyOptions: OptimizelyOptions = { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, eventProcessor: eventProcessor.createEventProcessor(eventProcessorConfig), logger, errorHandler, - datafileManager: config.sdkKey ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, + datafileManager: config.sdkKey + ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) + : undefined, notificationCenter, - isValidInstance: isValidInstance + isValidInstance: isValidInstance, + odpManager: BrowserOdpManager.createBrowserOdpManager({ logger, odpOptions: config.odpOptions }), }; - const optimizely = new Optimizely(optimizelyOptions); + const optimizely = new BrowserOptimizely(optimizelyOptions); try { if (typeof window.addEventListener === 'function') { @@ -143,13 +142,13 @@ const createInstance = function(config: Config): Client | null { ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { + } catch (e) { logger.error(enums.LOG_MESSAGES.UNABLE_TO_ATTACH_UNLOAD, MODULE_NAME, e.message); } return optimizely; // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { + } catch (e) { logger.error(e); return null; } @@ -163,8 +162,8 @@ const __internalResetRetryState = function(): void { * Entry point into the Optimizely Browser SDK */ -const setLogHandler = logHelper.setLogHandler -const setLogLevel = logHelper.setLogLevel +const setLogHandler = logHelper.setLogHandler; +const setLogLevel = logHelper.setLogLevel; export { loggerPlugin as logging, defaultErrorHandler as errorHandler, @@ -189,4 +188,4 @@ export default { OptimizelyDecideOption, }; -export * from './export_types' +export * from './export_types'; diff --git a/packages/optimizely-sdk/lib/optimizely/index.browser.ts b/packages/optimizely-sdk/lib/optimizely/index.browser.ts new file mode 100644 index 000000000..092950845 --- /dev/null +++ b/packages/optimizely-sdk/lib/optimizely/index.browser.ts @@ -0,0 +1,62 @@ +/**************************************************************************** + * Copyright 2023, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * https://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import OptimizelyUserContext from '../optimizely_user_context'; +import Optimizely from '.'; +import { OptimizelyOptions, UserAttributes } from '../shared_types'; +import { BrowserOdpManager } from '../plugins/odp_manager/index.browser'; + +export default class BrowserOptimizely extends Optimizely { + constructor(config: OptimizelyOptions) { + super(config); + } + + /** + * @override + * Creates a context of the user for which decision APIs will be called. + * + * A user context will be created successfully even when the SDK is not fully configured yet, however, + * for just the browser variant ODP Manager is expected to have been instantiated already in order to + * access the stored vuid. + * + * @param {string} userId (Optional) The user ID to be used for bucketing. Defaults to VUID. + * @param {UserAttributes} attributes (Optional) Arbitrary attributes map. + * @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or + * null if provided inputs are invalid + */ + createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { + const userIdentifier = userId || this.getVuid(); + + if (!userIdentifier) { + return null; + } + + return super.createUserContext(userIdentifier, attributes); + } + + /** + * @returns {string|undefined} Currently provisioned VUID from local ODP Manager or undefined if + * ODP Manager has not been instantiated yet for any reason. + */ + getVuid(): string | undefined { + if (!this.odpManager) { + this.logger?.error('Unable to get VUID - ODP Manager is not instantiated yet.'); + return undefined; + } + + return (this.odpManager as BrowserOdpManager).vuid; + } +} diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index e29a4af43..d4893a60a 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2022, Optimizely, Inc. and contributors * + * Copyright 2016-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -40,6 +40,8 @@ import { createForwardingEventProcessor } from '../plugins/event_processor/forwa import { createEventProcessor } from '../plugins/event_processor'; import { createNotificationCenter } from '../core/notification_center'; import { createHttpPollingDatafileManager } from '../plugins/datafile_manager/http_polling_datafile_manager'; +import { OdpManager } from '../core/odp/odp_manager'; +import { getLogger } from '../modules/logging'; var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; @@ -66,15 +68,17 @@ describe('lib/optimizely', function() { handleError: sinon.stub(), }; logging.setErrorHandler(globalStubErrorHandler); - ProjectConfigManagerStub = sinon.stub(projectConfigManager, 'createProjectConfigManager').callsFake(function(config) { - var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; - return { - stop: sinon.stub(), - getConfig: sinon.stub().returns(currentConfig), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns({ then: function() {} }), - }; - }); + ProjectConfigManagerStub = sinon + .stub(projectConfigManager, 'createProjectConfigManager') + .callsFake(function(config) { + var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; + return { + stop: sinon.stub(), + getConfig: sinon.stub().returns(currentConfig), + onUpdate: sinon.stub().returns(function() {}), + onReady: sinon.stub().returns({ then: function() {} }), + }; + }); sinon.stub(eventDispatcher, 'dispatchEvent'); clock = sinon.useFakeTimers(new Date()); }); @@ -258,7 +262,7 @@ describe('lib/optimizely', function() { sdkKey: '12345', datafileManager: createHttpPollingDatafileManager('12345', createdLogger), notificationCenter, - eventProcessor, + eventProcessor, }); sinon.assert.notCalled(stubErrorHandler.handleError); }); @@ -268,7 +272,7 @@ describe('lib/optimizely', function() { let datafileOptions = { autoUpdate: true, updateInterval: 2 * 60 * 1000, - } + }; let datafileManager = createHttpPollingDatafileManager('12345', createdLogger, undefined, datafileOptions); new Optimizely({ clientEngine: 'node-sdk', @@ -289,7 +293,7 @@ describe('lib/optimizely', function() { datafile: config, jsonSchemaValidator: jsonSchemaValidator, sdkKey: '12345', - datafileManager: datafileManager + datafileManager: datafileManager, }); }); }); @@ -304,7 +308,7 @@ describe('lib/optimizely', function() { jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, notificationCenter, - eventProcessor + eventProcessor, }); assert.instanceOf(optlyInstance, Optimizely); var optlyInstance2 = new Optimizely({ @@ -458,7 +462,6 @@ describe('lib/optimizely', function() { variation_key: 'variationWithAudience', enabled: true, }, - }, ], events: [ @@ -819,13 +822,18 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' + LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + 'DECISION_SERVICE', + 'testUser' ); sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperiment' + LOG_MESSAGES.NOT_ACTIVATING_USER, + 'OPTIMIZELY', + 'testUser', + 'testExperiment' ); }); @@ -835,19 +843,27 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' + LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + 'DECISION_SERVICE', + 'testUser' ); sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'testExperimentWithAudiences' + LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + 'DECISION_SERVICE', + 'testUser', + 'testExperimentWithAudiences' ); sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences' + LOG_MESSAGES.NOT_ACTIVATING_USER, + 'OPTIMIZELY', + 'testUser', + 'testExperimentWithAudiences' ); }); @@ -857,19 +873,27 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' + LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + 'DECISION_SERVICE', + 'testUser' ); sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'groupExperiment1' + LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + 'DECISION_SERVICE', + 'testUser', + 'groupExperiment1' ); sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'groupExperiment1' + LOG_MESSAGES.NOT_ACTIVATING_USER, + 'OPTIMIZELY', + 'testUser', + 'groupExperiment1' ); }); @@ -984,7 +1008,7 @@ describe('lib/optimizely', function() { var activate = optlyInstance.activate('testExperiment', 'user1'); assert.strictEqual(activate, 'control'); - sinon.assert.calledThrice(Optimizely.prototype.validateInputs); + sinon.assert.calledTwice(Optimizely.prototype.validateInputs); var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( @@ -1690,14 +1714,18 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( logCall1, LOG_LEVEL.WARNING, - LOG_MESSAGES.EVENT_KEY_NOT_FOUND, 'OPTIMIZELY', 'invalidEventKey' + LOG_MESSAGES.EVENT_KEY_NOT_FOUND, + 'OPTIMIZELY', + 'invalidEventKey' ); var logCall2 = createdLogger.log.getCall(1); sinon.assert.calledWithExactly( logCall2, LOG_LEVEL.WARNING, - LOG_MESSAGES.NOT_TRACKING_USER, 'OPTIMIZELY', 'testUser' + LOG_MESSAGES.NOT_TRACKING_USER, + 'OPTIMIZELY', + 'testUser' ); sinon.assert.notCalled(errorHandler.handleError); @@ -1781,7 +1809,9 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' + LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + 'DECISION_SERVICE', + 'testUser' ); }); @@ -1814,19 +1844,26 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' + LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + 'DECISION_SERVICE', + 'testUser' ); sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'testExperimentWithAudiences' + LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + 'DECISION_SERVICE', + 'testUser', + 'testExperimentWithAudiences' ); sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning' + LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, + 'DECISION_SERVICE', + 'testExperimentNotRunning' ); }); @@ -1883,7 +1920,7 @@ describe('lib/optimizely', function() { var getVariation = optlyInstance.getVariation('testExperiment', 'user1'); assert.strictEqual(getVariation, 'control'); - sinon.assert.calledTwice(Optimizely.prototype.validateInputs); + sinon.assert.calledOnce(Optimizely.prototype.validateInputs); sinon.assert.calledTwice(createdLogger.log); @@ -2458,9 +2495,9 @@ describe('lib/optimizely', function() { variation_id: '111129', metadata: { flag_key: '', - rule_key: "testExperiment", - rule_type: "experiment", - variation_key: "variation", + rule_key: 'testExperiment', + rule_type: 'experiment', + variation_key: 'variation', enabled: true, }, }, @@ -2520,9 +2557,9 @@ describe('lib/optimizely', function() { variation_id: '111129', metadata: { flag_key: '', - rule_key: "testExperiment", - rule_type: "experiment", - variation_key: "variation", + rule_key: 'testExperiment', + rule_type: 'experiment', + variation_key: 'variation', enabled: true, }, }, @@ -3048,7 +3085,9 @@ describe('lib/optimizely', function() { createdLogger.log, LOG_LEVEL.INFO, '%s: Feature %s is not enabled for user %s.', - 'OPTIMIZELY', 'test_feature', 'user1' + 'OPTIMIZELY', + 'test_feature', + 'user1' ); var expectedArguments = { @@ -3240,7 +3279,7 @@ describe('lib/optimizely', function() { variableKey: 'button_info', variableValue: { num_buttons: 1, - text: "first variation", + text: 'first variation', }, variableType: FEATURE_VARIABLE_TYPES.JSON, source: DECISION_SOURCES.FEATURE_TEST, @@ -3394,11 +3433,9 @@ describe('lib/optimizely', function() { }); it('returns the right value from getAllFeatureVariables and send notification with featureEnabled true', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature_for_experiment', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature_for_experiment', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { is_button_animated: true, button_width: 20.25, @@ -3597,11 +3634,9 @@ describe('lib/optimizely', function() { }); it('returns the right value from getAllFeatureVariables and send notification with featureEnabled false', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature_for_experiment', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature_for_experiment', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { is_button_animated: false, button_width: 50.55, @@ -3883,11 +3918,9 @@ describe('lib/optimizely', function() { }); it('returns the right value from getAllFeatureVariables and send notification with featureEnabled true', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { new_content: true, price: 4.99, @@ -4031,7 +4064,7 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); sinon.assert.calledWith(decisionListener, { type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, @@ -4161,11 +4194,9 @@ describe('lib/optimizely', function() { }); it('returns the default value from getAllFeatureVariables and send notification with featureEnabled false', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { new_content: false, price: 14.99, @@ -4456,11 +4487,9 @@ describe('lib/optimizely', function() { }); it('returns the default value from getAllFeatureVariables and send notification with featureEnabled false', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature_for_experiment', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature_for_experiment', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { is_button_animated: false, button_width: 50.55, @@ -4469,7 +4498,7 @@ describe('lib/optimizely', function() { button_info: { num_buttons: 0, text: 'default value', - } + }, }); sinon.assert.calledWith(decisionListener, { type: DECISION_NOTIFICATION_TYPES.ALL_FEATURE_VARIABLES, @@ -4486,7 +4515,7 @@ describe('lib/optimizely', function() { button_info: { num_buttons: 0, text: 'default value', - } + }, }, source: DECISION_SOURCES.ROLLOUT, sourceInfo: {}, @@ -4562,7 +4591,7 @@ describe('lib/optimizely', function() { }); it('should create multiple instances of OptimizelyUserContext', function() { - var userId1 = 'testUser1' + var userId1 = 'testUser1'; var userId2 = 'testUser2'; var attributes1 = { test_attribute: 'test_value' }; var user1 = optlyInstance.createUserContext(userId1, attributes1); @@ -4643,8 +4672,8 @@ describe('lib/optimizely', function() { ruleKey: null, flagKey: flagKey, userContext: user, - reasons: [ sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, flagKey) ], - } + reasons: [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, flagKey)], + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); @@ -4664,8 +4693,8 @@ describe('lib/optimizely', function() { ruleKey: null, flagKey: flagKey, userContext: user, - reasons: [ DECISION_MESSAGES.SDK_NOT_READY ], - } + reasons: [DECISION_MESSAGES.SDK_NOT_READY], + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); @@ -4686,7 +4715,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); var expectedImpressionEvent = { @@ -4743,7 +4772,7 @@ describe('lib/optimizely', function() { }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4) + sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, @@ -4754,14 +4783,14 @@ describe('lib/optimizely', function() { decisionInfo: { flagKey: 'feature_2', enabled: true, - ruleKey: "exp_no_audience", - variationKey: "variation_with_traffic", + ruleKey: 'exp_no_audience', + variationKey: 'variation_with_traffic', variables: { i_42: 42 }, decisionEventDispatched: true, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -4772,7 +4801,7 @@ describe('lib/optimizely', function() { optimizely: optlyInstance, userId, }); - var decision = optlyInstance.decide(user, flagKey, [ OptimizelyDecideOption.DISABLE_DECISION_EVENT ]); + var decision = optlyInstance.decide(user, flagKey, [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); var expectedDecision = { variationKey: 'variation_with_traffic', enabled: true, @@ -4781,7 +4810,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); @@ -4795,14 +4824,14 @@ describe('lib/optimizely', function() { decisionInfo: { flagKey: 'feature_2', enabled: true, - ruleKey: "exp_no_audience", - variationKey: "variation_with_traffic", + ruleKey: 'exp_no_audience', + variationKey: 'variation_with_traffic', variables: { i_42: 42 }, decisionEventDispatched: false, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -4812,7 +4841,10 @@ describe('lib/optimizely', function() { optimizely: optlyInstance, userId, }); - var decision = optlyInstance.decide(user, flagKey, [ OptimizelyDecideOption.DISABLE_DECISION_EVENT, OptimizelyDecideOption.EXCLUDE_VARIABLES ]); + var decision = optlyInstance.decide(user, flagKey, [ + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + OptimizelyDecideOption.EXCLUDE_VARIABLES, + ]); var expectedDecision = { variationKey: 'variation_with_traffic', enabled: true, @@ -4821,7 +4853,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); sinon.assert.calledOnce(optlyInstance.notificationCenter.sendNotifications); @@ -4836,13 +4868,13 @@ describe('lib/optimizely', function() { flagKey: 'feature_2', enabled: true, ruleKey: 'exp_no_audience', - variationKey: "variation_with_traffic", + variationKey: 'variation_with_traffic', variables: {}, decisionEventDispatched: false, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -4862,7 +4894,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); @@ -4882,8 +4914,8 @@ describe('lib/optimizely', function() { decisionEventDispatched: true, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -4906,7 +4938,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); @@ -4926,8 +4958,8 @@ describe('lib/optimizely', function() { decisionEventDispatched: false, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -4947,7 +4979,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); @@ -4967,8 +4999,8 @@ describe('lib/optimizely', function() { decisionEventDispatched: true, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); @@ -4984,7 +5016,7 @@ describe('lib/optimizely', function() { logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.EXCLUDE_VARIABLES ], + defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], eventProcessor, notificationCenter, }); @@ -5006,7 +5038,7 @@ describe('lib/optimizely', function() { var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); var expectedDecisionObj = { @@ -5017,7 +5049,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); sinon.assert.calledThrice(optlyInstance.notificationCenter.sendNotifications); @@ -5037,8 +5069,8 @@ describe('lib/optimizely', function() { decisionEventDispatched: true, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -5046,9 +5078,9 @@ describe('lib/optimizely', function() { var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); - var decision = optlyInstance.decide(user, flagKey, [ OptimizelyDecideOption.DISABLE_DECISION_EVENT ]); + var decision = optlyInstance.decide(user, flagKey, [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); var expectedDecisionObj = { variationKey: 'variation_with_traffic', enabled: true, @@ -5057,7 +5089,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.notCalled(eventDispatcher.dispatchEvent); sinon.assert.calledOnce(optlyInstance.notificationCenter.sendNotifications); @@ -5077,8 +5109,8 @@ describe('lib/optimizely', function() { decisionEventDispatched: false, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); @@ -5094,7 +5126,7 @@ describe('lib/optimizely', function() { logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.DISABLE_DECISION_EVENT ], + defaultDecideOptions: [OptimizelyDecideOption.DISABLE_DECISION_EVENT], notificationCenter, eventProcessor, }); @@ -5108,10 +5140,10 @@ describe('lib/optimizely', function() { it('should make a decision and do not dispatch an event', function() { var flagKey = 'feature_2'; - var expectedVariables= optlyInstance.getAllFeatureVariables(flagKey, userId); + var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); var expectedDecisionObj = { @@ -5122,7 +5154,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.notCalled(eventDispatcher.dispatchEvent); sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); @@ -5142,8 +5174,8 @@ describe('lib/optimizely', function() { decisionEventDispatched: false, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); @@ -5159,7 +5191,7 @@ describe('lib/optimizely', function() { logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.INCLUDE_REASONS ], + defaultDecideOptions: [OptimizelyDecideOption.INCLUDE_REASONS], notificationCenter, eventProcessor, }); @@ -5173,20 +5205,16 @@ describe('lib/optimizely', function() { it('should include reason when experiment is not running', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); - newConfig.experiments[0].status = "NotRunning"; + newConfig.experiments[0].status = 'NotRunning'; optlyInstance.projectConfigManager.getConfig.returns(newConfig); - var flagKey = "feature_1"; + var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, - 'DECISION_SERVICE', - 'exp_with_audience' - ) + sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'exp_with_audience') ); }); @@ -5199,12 +5227,13 @@ describe('lib/optimizely', function() { lookup: sinon.stub().returns({ user_id: userId, experiment_bucket_map: { - '10420810910': { // "exp_no_audience" + '10420810910': { + // "exp_no_audience" variation_id: variationId2, }, }, }), - save: sinon.stub() + save: sinon.stub(), }; var optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', @@ -5216,23 +5245,17 @@ describe('lib/optimizely', function() { logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.INCLUDE_REASONS ], + defaultDecideOptions: [OptimizelyDecideOption.INCLUDE_REASONS], notificationCenter, eventProcessor, }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision = optlyInstanceWithUserProfile.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.RETURNING_STORED_VARIATION, - 'DECISION_SERVICE', - variationKey2, - experimentKey, - userId - ) + sprintf(LOG_MESSAGES.RETURNING_STORED_VARIATION, 'DECISION_SERVICE', variationKey2, experimentKey, userId) ); }); @@ -5244,16 +5267,11 @@ describe('lib/optimizely', function() { optlyInstance.projectConfigManager.getConfig.returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_FORCED_IN_VARIATION, - 'DECISION_SERVICE', - userId, - variationKey - ) + sprintf(LOG_MESSAGES.USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', userId, variationKey) ); }); @@ -5267,17 +5285,11 @@ describe('lib/optimizely', function() { optlyInstance.projectConfigManager.getConfig.returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_VARIATION, - 'DECISION_SERVICE', - variationKey, - experimentKey, - userId - ) + sprintf(LOG_MESSAGES.USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', variationKey, experimentKey, userId) ); }); @@ -5289,16 +5301,11 @@ describe('lib/optimizely', function() { optlyInstance.projectConfigManager.getConfig.returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.FORCED_BUCKETING_FAILED, - 'DECISION_SERVICE', - variationKey, - userId - ) + sprintf(LOG_MESSAGES.FORCED_BUCKETING_FAILED, 'DECISION_SERVICE', variationKey, userId) ); }); @@ -5306,17 +5313,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - '1' - ) + sprintf(LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') ); }); @@ -5324,17 +5326,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'CA'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - '1' - ) + sprintf(LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') ); }); @@ -5342,17 +5339,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_IN_ROLLOUT, - 'DECISION_SERVICE', - userId, - flagKey - ) + sprintf(LOG_MESSAGES.USER_IN_ROLLOUT, 'DECISION_SERVICE', userId, flagKey) ); }); @@ -5360,17 +5352,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'KO'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - 'Everyone Else' - ) + sprintf(LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, 'Everyone Else') ); }); @@ -5378,17 +5365,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('browser', 'safari'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - '2' - ) + sprintf(LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, 'DECISION_SERVICE', userId, '2') ); }); @@ -5398,17 +5380,11 @@ describe('lib/optimizely', function() { var variationKey = 'variation_with_traffic'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_HAS_VARIATION, - 'DECISION_SERVICE', - userId, - variationKey, - experimentKey, - ) + sprintf(LOG_MESSAGES.USER_HAS_VARIATION, 'DECISION_SERVICE', userId, variationKey, experimentKey) ); }); @@ -5417,21 +5393,16 @@ describe('lib/optimizely', function() { var experimentKey = 'exp_no_audience'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[1].trafficAllocation = []; - newConfig.experiments[1].trafficAllocation.push({ endOfRange: 0, entityId: 'any' }) + newConfig.experiments[1].trafficAllocation.push({ endOfRange: 0, entityId: 'any' }); optlyInstance.projectConfigManager.getConfig.returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attributes: { 'age': 25 }, + attributes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_HAS_NO_VARIATION, - 'DECISION_SERVICE', - userId, - experimentKey, - ) + sprintf(LOG_MESSAGES.USER_HAS_NO_VARIATION, 'DECISION_SERVICE', userId, experimentKey) ); }); @@ -5449,13 +5420,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - 'BUCKETER', - userId, - experimentKey, - groupId - ) + sprintf(LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'BUCKETER', userId, experimentKey, groupId) ); }); @@ -5470,11 +5435,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, - 'DECISION_SERVICE', - flagKey - ) + sprintf(LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, 'DECISION_SERVICE', flagKey) ); }); @@ -5487,12 +5448,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, - 'DECISION_SERVICE', - userId, - experimentKey - ) + sprintf(LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', userId, experimentKey) ); }); @@ -5531,7 +5487,7 @@ describe('lib/optimizely', function() { var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'country': 25 } + attirutes: { country: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( @@ -5556,7 +5512,7 @@ describe('lib/optimizely', function() { var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 10000 } + attirutes: { age: 10000 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( @@ -5581,7 +5537,7 @@ describe('lib/optimizely', function() { var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( @@ -5606,7 +5562,7 @@ describe('lib/optimizely', function() { var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( @@ -5631,7 +5587,7 @@ describe('lib/optimizely', function() { var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( @@ -5656,7 +5612,7 @@ describe('lib/optimizely', function() { var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( @@ -5680,7 +5636,7 @@ describe('lib/optimizely', function() { optlyInstance.projectConfigManager.getConfig.returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( @@ -5699,7 +5655,7 @@ describe('lib/optimizely', function() { var mockUserProfileServiceInstance; var optlyInstanceWithUserProfile; it('should bucket if there was no previously bucketed variation and save bucketing decision to the user profile', function() { - var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' + var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' var variationId1 = '10418551353'; var variationKey1 = 'variation_with_traffic'; mockUserProfileServiceInstance = { @@ -5707,7 +5663,7 @@ describe('lib/optimizely', function() { user_id: userId, experiment_bucket_map: {}, }), - save: sinon.stub() + save: sinon.stub(), }; optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', @@ -5724,7 +5680,7 @@ describe('lib/optimizely', function() { }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision1 = optlyInstanceWithUserProfile.decide(user, flagKey); // should return variationId1 as no stored variation exists @@ -5735,7 +5691,7 @@ describe('lib/optimizely', function() { describe('with IGNORE_USER_PROFILE_SERVICE flag in decide options', function() { it('should bypass user profile service', function() { - var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' + var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' var variationId1 = '10418551353'; var variationId2 = '10418510624'; var variationKey1 = 'variation_with_traffic'; @@ -5744,12 +5700,13 @@ describe('lib/optimizely', function() { lookup: sinon.stub().returns({ user_id: userId, experiment_bucket_map: { - '10420810910': { // 'exp_no_audience' + '10420810910': { + // 'exp_no_audience' variation_id: variationId2, }, }, }), - save: sinon.stub() + save: sinon.stub(), }; optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', @@ -5766,12 +5723,14 @@ describe('lib/optimizely', function() { }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision1 = optlyInstanceWithUserProfile.decide(user, flagKey); // should return variationId2 set by UPS assert.equal(variationKey2, decision1.variationKey); - var decision2 = optlyInstanceWithUserProfile.decide(user, flagKey, [ OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE ]); + var decision2 = optlyInstanceWithUserProfile.decide(user, flagKey, [ + OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE, + ]); // should ignore variationId2 set by UPS and return variationId1 assert.equal(variationKey1, decision2.variationKey); // also should not save either @@ -5781,19 +5740,20 @@ describe('lib/optimizely', function() { describe('with IGNORE_USER_PROFILE_SERVICE flag in default decide options', function() { it('should bypass user profile service', function() { - var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' + var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' var variationId2 = '10418510624'; var variationKey1 = 'variation_with_traffic'; mockUserProfileServiceInstance = { lookup: sinon.stub().returns({ user_id: userId, experiment_bucket_map: { - '10420810910': { // 'exp_no_audience' + '10420810910': { + // 'exp_no_audience' variation_id: variationId2, }, }, }), - save: sinon.stub() + save: sinon.stub(), }; optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', @@ -5805,13 +5765,13 @@ describe('lib/optimizely', function() { logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE ], + defaultDecideOptions: [OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE], notificationCenter, eventProcessor, }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision = optlyInstanceWithUserProfile.decide(user, flagKey); // should ignore variationId2 set by UPS and return variationId1 @@ -5851,7 +5811,7 @@ describe('lib/optimizely', function() { var flagKey = 'feature_2'; var user = optlyInstance.createUserContext(userId); var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); - var decisionsMap = optlyInstance.decideForKeys(user, [ flagKey ]); + var decisionsMap = optlyInstance.decideForKeys(user, [flagKey]); var decision = decisionsMap[flagKey]; var expectedDecision = { variationKey: 'variation_with_traffic', @@ -5861,11 +5821,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 1); assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4) + sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; var decisionEventDispatched = notificationCallArgs[1].decisionInfo.decisionEventDispatched; assert.deepEqual(decisionEventDispatched, true); @@ -5887,7 +5847,7 @@ describe('lib/optimizely', function() { flagKey: flagKeysArray[0], userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -5896,7 +5856,7 @@ describe('lib/optimizely', function() { flagKey: flagKeysArray[1], userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -5908,7 +5868,11 @@ describe('lib/optimizely', function() { var flagKey2 = 'feature_3'; var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey1, userId); - var decisionsMap = optlyInstance.decideForKeys(user, [ flagKey1, flagKey2 ], [ OptimizelyDecideOption.ENABLED_FLAGS_ONLY ]); + var decisionsMap = optlyInstance.decideForKeys( + user, + [flagKey1, flagKey2], + [OptimizelyDecideOption.ENABLED_FLAGS_ONLY] + ); var decision = decisionsMap[flagKey1]; var expectedDecision = { variationKey: 'variation_with_traffic', @@ -5918,7 +5882,7 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 1); assert.deepEqual(decision, expectedDecision); sinon.assert.calledTwice(eventDispatcher.dispatchEvent); @@ -5969,7 +5933,7 @@ describe('lib/optimizely', function() { flagKey: allFlagKeysArray[0], userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -5978,7 +5942,7 @@ describe('lib/optimizely', function() { flagKey: allFlagKeysArray[1], userContext: user, reasons: [], - } + }; var expectedDecision3 = { variationKey: null, enabled: false, @@ -5987,7 +5951,7 @@ describe('lib/optimizely', function() { flagKey: allFlagKeysArray[2], userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, allFlagKeysArray.length); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -6001,7 +5965,7 @@ describe('lib/optimizely', function() { var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKey1, userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKey2, userId); - var decisionsMap = optlyInstance.decideAll(user, [ OptimizelyDecideOption.ENABLED_FLAGS_ONLY ]); + var decisionsMap = optlyInstance.decideAll(user, [OptimizelyDecideOption.ENABLED_FLAGS_ONLY]); var decision1 = decisionsMap[flagKey1]; var decision2 = decisionsMap[flagKey2]; var expectedDecision1 = { @@ -6012,7 +5976,7 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -6021,7 +5985,7 @@ describe('lib/optimizely', function() { flagKey: flagKey2, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -6040,7 +6004,7 @@ describe('lib/optimizely', function() { logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.ENABLED_FLAGS_ONLY ], + defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY], eventProcessor, notificationCenter, }); @@ -6069,7 +6033,7 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -6078,7 +6042,7 @@ describe('lib/optimizely', function() { flagKey: flagKey2, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -6089,7 +6053,7 @@ describe('lib/optimizely', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; var user = optlyInstance.createUserContext(userId, { gender: 'female' }); - var decisionsMap = optlyInstance.decideAll(user, [ OptimizelyDecideOption.EXCLUDE_VARIABLES ]); + var decisionsMap = optlyInstance.decideAll(user, [OptimizelyDecideOption.EXCLUDE_VARIABLES]); var decision1 = decisionsMap[flagKey1]; var decision2 = decisionsMap[flagKey2]; var expectedDecision1 = { @@ -6100,7 +6064,7 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -6109,7 +6073,7 @@ describe('lib/optimizely', function() { flagKey: flagKey2, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -6243,7 +6207,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', 'user1'); assert.strictEqual(result, false); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Optimizely object is not valid. Failing isFeatureEnabled.' ); }); @@ -6341,7 +6305,7 @@ describe('lib/optimizely', function() { }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - assert.isFunction(callArgs[1]); + assert.isFunction(callArgs[1]); assert.equal( buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Feature test_feature_for_experiment is enabled for user user1.' @@ -6680,7 +6644,7 @@ describe('lib/optimizely', function() { assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' ); }); @@ -6693,7 +6657,7 @@ describe('lib/optimizely', function() { assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' ); }); @@ -6787,7 +6751,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getEnabledFeatures('user1', { test_attribute: 'test_value' }); assert.deepEqual(result, []); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Optimizely object is not valid. Failing getEnabledFeatures.' ); }); @@ -6831,15 +6795,13 @@ describe('lib/optimizely', function() { optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionListener); var result = optlyInstance.getEnabledFeatures('test_user', attributes); assert.strictEqual(result.length, 5); - assert.deepEqual(result, - [ - 'test_feature_2', - 'test_feature_for_experiment', - 'shared_feature', - 'test_feature_in_exclusion_group', - 'test_feature_in_multiple_experiments' - ] - ); + assert.deepEqual(result, [ + 'test_feature_2', + 'test_feature_for_experiment', + 'shared_feature', + 'test_feature_in_exclusion_group', + 'test_feature_in_multiple_experiments', + ]); sinon.assert.calledWithExactly(decisionListener.getCall(0), { type: DECISION_NOTIFICATION_TYPES.FEATURE, @@ -7097,7 +7059,7 @@ describe('lib/optimizely', function() { 'OPTIMIZELY', '2', 'num_buttons', - 'test_feature_for_experiment', + 'test_feature_for_experiment' ); sinon.assert.calledWith( createdLogger.log, @@ -7106,7 +7068,7 @@ describe('lib/optimizely', function() { 'OPTIMIZELY', 'true', 'is_button_animated', - 'test_feature_for_experiment', + 'test_feature_for_experiment' ); sinon.assert.calledWith( createdLogger.log, @@ -7151,7 +7113,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, false); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' ); }); @@ -7162,7 +7124,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 50.55); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' ); }); @@ -7173,7 +7135,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 10); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' ); }); @@ -7184,7 +7146,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 'Buy me'); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' ); }); @@ -7198,7 +7160,7 @@ describe('lib/optimizely', function() { text: 'default value', }); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' ); }); @@ -7212,7 +7174,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, false); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' ); }); @@ -7226,7 +7188,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, 50.55); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' ); }); @@ -7240,7 +7202,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, 10); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' ); }); @@ -7254,21 +7216,18 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, 'Buy me'); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' ); }); it('returns the variable default value from getFeatureVariableJSON', function() { - var result = optlyInstance.getFeatureVariableJSON( - 'test_feature_for_experiment', - 'button_info', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { num_buttons: 0, - text: "default value", + text: 'default value', }); assert.equal( buildLogMessageFromArgs(createdLogger.log.lastCall.args), @@ -7287,7 +7246,7 @@ describe('lib/optimizely', function() { button_txt: 'Buy me', button_info: { num_buttons: 0, - text: "default value", + text: 'default value', }, }); sinon.assert.calledWith( @@ -7406,7 +7365,7 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { num_buttons: 0, - text: "default value", + text: 'default value', }); assert.equal( buildLogMessageFromArgs(createdLogger.log.lastCall.args), @@ -7492,7 +7451,7 @@ describe('lib/optimizely', function() { button_txt: 'Buy me', button_info: { num_buttons: 0, - text: "default value", + text: 'default value', }, }); assert.deepEqual(createdLogger.log.args, [ @@ -7502,7 +7461,7 @@ describe('lib/optimizely', function() { 'OPTIMIZELY', 'test_feature_for_experiment', 'user1', - '10' + '10', ], [ LOG_LEVEL.INFO, @@ -7510,7 +7469,7 @@ describe('lib/optimizely', function() { 'OPTIMIZELY', 'test_feature_for_experiment', 'user1', - 'false' + 'false', ], [ LOG_LEVEL.INFO, @@ -7518,7 +7477,7 @@ describe('lib/optimizely', function() { 'OPTIMIZELY', 'test_feature_for_experiment', 'user1', - 'Buy me' + 'Buy me', ], [ LOG_LEVEL.INFO, @@ -7526,7 +7485,7 @@ describe('lib/optimizely', function() { 'OPTIMIZELY', 'test_feature_for_experiment', 'user1', - '50.55' + '50.55', ], [ LOG_LEVEL.INFO, @@ -7534,9 +7493,9 @@ describe('lib/optimizely', function() { 'OPTIMIZELY', 'test_feature_for_experiment', 'user1', - '{ "num_buttons": 0, "text": "default value"}' - ] - ]); + '{ "num_buttons": 0, "text": "default value"}', + ], + ]); }); }); }); @@ -7691,65 +7650,62 @@ describe('lib/optimizely', function() { message: 'Hello audience', }, }); - assert.deepEqual( - createdLogger.log.args, + assert.deepEqual(createdLogger.log.args, [ [ - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'true', - 'new_content', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '395', - 'lasers', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '4.99', - 'price', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'Hello audience', - 'message', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '{ "count": 2, "message": "Hello audience" }', - 'message_info', - 'test_feature' - ] - ] - ) - }); - - describe('when the variable is not used in the variation', function() { - beforeEach(function() { - sandbox.stub(projectConfig, 'getVariableValueForVariation').returns(null); - }); - - it('returns the variable default value from getFeatureVariable when variable type is boolean', function() { - var result = optlyInstance.getFeatureVariable('test_feature', 'new_content', 'user1', { + LOG_LEVEL.INFO, + '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + 'OPTIMIZELY', + 'true', + 'new_content', + 'test_feature', + ], + [ + LOG_LEVEL.INFO, + '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + 'OPTIMIZELY', + '395', + 'lasers', + 'test_feature', + ], + [ + LOG_LEVEL.INFO, + '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + 'OPTIMIZELY', + '4.99', + 'price', + 'test_feature', + ], + [ + LOG_LEVEL.INFO, + '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + 'OPTIMIZELY', + 'Hello audience', + 'message', + 'test_feature', + ], + [ + LOG_LEVEL.INFO, + '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + 'OPTIMIZELY', + '{ "count": 2, "message": "Hello audience" }', + 'message_info', + 'test_feature', + ], + ]); + }); + + describe('when the variable is not used in the variation', function() { + beforeEach(function() { + sandbox.stub(projectConfig, 'getVariableValueForVariation').returns(null); + }); + + it('returns the variable default value from getFeatureVariable when variable type is boolean', function() { + var result = optlyInstance.getFeatureVariable('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', }); assert.strictEqual(result, false); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' ); }); @@ -7760,7 +7716,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 14.99); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' ); }); @@ -7771,7 +7727,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 400); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' ); }); @@ -7782,7 +7738,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 'Hello'); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' ); }); @@ -7796,7 +7752,7 @@ describe('lib/optimizely', function() { message: 'Hello', }); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' ); }); @@ -7807,7 +7763,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, false); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' ); }); @@ -7818,7 +7774,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 14.99); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' ); }); @@ -7829,7 +7785,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 400); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' ); }); @@ -7840,7 +7796,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 'Hello'); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' ); }); @@ -7851,10 +7807,10 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' ); }); @@ -7873,47 +7829,43 @@ describe('lib/optimizely', function() { message: 'Hello', }, }); - assert.deepEqual( - createdLogger.log.args, + assert.deepEqual(createdLogger.log.args, [ [ - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'new_content', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'lasers', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'price', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'message', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'message_info', - '594032' - ] - ] - - ) + LOG_LEVEL.INFO, + '%s: Variable "%s" is not used in variation "%s". Returning default value.', + 'OPTIMIZELY', + 'new_content', + '594032', + ], + [ + LOG_LEVEL.INFO, + '%s: Variable "%s" is not used in variation "%s". Returning default value.', + 'OPTIMIZELY', + 'lasers', + '594032', + ], + [ + LOG_LEVEL.INFO, + '%s: Variable "%s" is not used in variation "%s". Returning default value.', + 'OPTIMIZELY', + 'price', + '594032', + ], + [ + LOG_LEVEL.INFO, + '%s: Variable "%s" is not used in variation "%s". Returning default value.', + 'OPTIMIZELY', + 'message', + '594032', + ], + [ + LOG_LEVEL.INFO, + '%s: Variable "%s" is not used in variation "%s". Returning default value.', + 'OPTIMIZELY', + 'message_info', + '594032', + ], + ]); }); }); }); @@ -7987,7 +7939,7 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); assert.equal( buildLogMessageFromArgs(createdLogger.log.lastCall.args), @@ -8045,7 +7997,7 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); assert.equal( buildLogMessageFromArgs(createdLogger.log.lastCall.args), @@ -8067,51 +8019,48 @@ describe('lib/optimizely', function() { message: 'Hello', }, }); - assert.deepEqual( - createdLogger.log.args, + assert.deepEqual(createdLogger.log.args, [ [ - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - 'false' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '400' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '14.99' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - 'Hello' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '{ "count": 1, "message": "Hello" }' - ] - ] - ) + LOG_LEVEL.INFO, + '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + 'OPTIMIZELY', + 'test_feature', + 'user1', + 'false', + ], + [ + LOG_LEVEL.INFO, + '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + 'OPTIMIZELY', + 'test_feature', + 'user1', + '400', + ], + [ + LOG_LEVEL.INFO, + '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + 'OPTIMIZELY', + 'test_feature', + 'user1', + '14.99', + ], + [ + LOG_LEVEL.INFO, + '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + 'OPTIMIZELY', + 'test_feature', + 'user1', + 'Hello', + ], + [ + LOG_LEVEL.INFO, + '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + 'OPTIMIZELY', + 'test_feature', + 'user1', + '{ "count": 1, "message": "Hello" }', + ], + ]); }); }); }); @@ -8136,7 +8085,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, false); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' ); }); @@ -8147,7 +8096,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 50.55); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' ); }); @@ -8158,7 +8107,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 10); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' ); }); @@ -8169,7 +8118,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 'Buy me'); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' ); }); @@ -8183,7 +8132,7 @@ describe('lib/optimizely', function() { text: 'default value', }); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' ); }); @@ -8197,7 +8146,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, false); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' ); }); @@ -8208,7 +8157,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 50.55); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' ); }); @@ -8219,7 +8168,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 10); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' ); }); @@ -8230,7 +8179,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, 'Buy me'); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' ); }); @@ -8244,7 +8193,7 @@ describe('lib/optimizely', function() { text: 'default value', }); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' ); }); @@ -8263,51 +8212,48 @@ describe('lib/optimizely', function() { text: 'default value', }, }); - assert.deepEqual( - createdLogger.log.args, + assert.deepEqual(createdLogger.log.args, [ [ - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'num_buttons', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'is_button_animated', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_txt', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_width', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_info', - 'test_feature_for_experiment' - ] - ] - ); + LOG_LEVEL.INFO, + '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + 'OPTIMIZELY', + 'user1', + 'num_buttons', + 'test_feature_for_experiment', + ], + [ + LOG_LEVEL.INFO, + '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + 'OPTIMIZELY', + 'user1', + 'is_button_animated', + 'test_feature_for_experiment', + ], + [ + LOG_LEVEL.INFO, + '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + 'OPTIMIZELY', + 'user1', + 'button_txt', + 'test_feature_for_experiment', + ], + [ + LOG_LEVEL.INFO, + '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + 'OPTIMIZELY', + 'user1', + 'button_width', + 'test_feature_for_experiment', + ], + [ + LOG_LEVEL.INFO, + '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + 'OPTIMIZELY', + 'user1', + 'button_info', + 'test_feature_for_experiment', + ], + ]); }); }); @@ -8317,7 +8263,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8328,7 +8274,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8337,7 +8283,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'is_button_animated'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8348,7 +8294,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8359,7 +8305,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8368,7 +8314,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8379,7 +8325,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8390,7 +8336,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8399,7 +8345,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8410,7 +8356,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8421,7 +8367,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8430,7 +8376,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8441,7 +8387,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8452,7 +8398,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8461,7 +8407,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8524,7 +8470,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8538,7 +8484,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8547,7 +8493,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8558,7 +8504,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8569,7 +8515,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8578,7 +8524,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8589,7 +8535,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8600,7 +8546,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8609,7 +8555,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8620,7 +8566,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8631,7 +8577,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8640,7 +8586,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8651,7 +8597,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8662,7 +8608,7 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8671,7 +8617,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_info'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Provided user_id is in an invalid format.' ); }); @@ -8746,7 +8692,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8755,7 +8701,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_width', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8764,7 +8710,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'num_buttons', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8773,7 +8719,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_txt', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8782,7 +8728,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_info', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8795,7 +8741,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' ); }); @@ -8804,7 +8750,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableBoolean('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8813,7 +8759,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableDouble('thisIsNotAValidKey<><><>', 'button_width', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8822,7 +8768,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableInteger('thisIsNotAValidKey<><><>', 'num_buttons', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8831,7 +8777,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableString('thisIsNotAValidKey<><><>', 'button_txt', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8840,7 +8786,7 @@ describe('lib/optimizely', function() { var result = optlyInstance.getFeatureVariableJSON('thisIsNotAValidKey<><><>', 'button_info', 'user1'); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -8853,7 +8799,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' ); }); @@ -8866,7 +8812,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' ); }); @@ -8879,7 +8825,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' ); }); @@ -8892,7 +8838,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' ); }); @@ -8905,7 +8851,7 @@ describe('lib/optimizely', function() { ); assert.strictEqual(result, null); assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), + buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' ); }); @@ -9271,7 +9217,10 @@ describe('lib/optimizely', function() { optlyInstance.projectConfigManager.getConfig().audiencesById, sinon.match.any ); - assert.deepEqual(evalSpy.getCalls()[0].args[2].getAttributes(), { house: '...Slytherinnn...sss.', favorite_ice_cream: 'matcha' }); + assert.deepEqual(evalSpy.getCalls()[0].args[2].getAttributes(), { + house: '...Slytherinnn...sss.', + favorite_ice_cream: 'matcha', + }); }); it('can exclude a user from a rollout with complex audience conditions via isFeatureEnabled', function() { @@ -9678,7 +9627,7 @@ describe('lib/optimizely', function() { process: sinon.stub(), start: sinon.stub(), stop: sinon.stub(), - }; + }; }); describe('when the event processor stop method returns a promise that fulfills', function() { @@ -10094,7 +10043,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', datafile: testData.getTestProjectConfig(), - errorHandler, + errorHandler, logger, isValidInstance: true, eventBatchSize: 1, @@ -10138,4 +10087,102 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly(notificationListener, eventDispatcherSpy.getCall(0).args[0]); }); }); + + // TODO: Finish these tests in ODP Node.js Implementation + describe('odp', () => { + var optlyInstanceWithOdp; + var bucketStub; + var fakeDecisionResponse; + var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); + var eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); + var createdLogger = logger.createLogger({ + logLevel: LOG_LEVEL.INFO, + logToConsole: false, + }); + + beforeEach(function() { + optlyInstanceWithOdp = new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestProjectConfig(), + errorHandler: errorHandler, + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + eventProcessor, + notificationCenter, + odpManager: new OdpManager({ + disable: false, + }), + }); + + bucketStub = sinon.stub(bucketer, 'bucket'); + sinon.stub(errorHandler, 'handleError'); + sinon.stub(createdLogger, 'log'); + sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); + }); + + afterEach(function() { + bucketer.bucket.restore(); + errorHandler.handleError.restore(); + createdLogger.log.restore(); + fns.uuid.restore(); + }); + + it('should call logger with log level of "info" when odp disabled', () => { + new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestProjectConfig(), + errorHandler: errorHandler, + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + eventProcessor, + notificationCenter, + odpManager: new OdpManager({ + disable: true, + logger: createdLogger, + }), + }); + + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, LOG_MESSAGES.ODP_DISABLED); + }); + + it('should send an identify event when called with odp enabled', () => { + //... + }); + + it('should flush the odp event queue as part of the close() function call', () => { + //... + }); + + describe('odp manager overrides', () => { + it('should accept custom cache size and timeout overrides defined in odp service config', () => { + //... + }); + + it('should accept a valid custom cache', () => { + //... + }); + + it('should call logger with log level of "error" when custom cache is invalid', () => { + //... + }); + + it('should accept a custom segment mananger override defined in odp service config', () => { + //... + }); + + it('should accept a custom event manager override defined in odp service config', () => { + //... + }); + + it('should call logger with log level of "error" when odp service config is invalid', () => { + //... + }); + }); + }); }); diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 8d6b6ccd4..f1cd77ab3 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -17,10 +17,12 @@ import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; -import { EventProcessor } from '../../lib/modules/event_processor'; +import { EventProcessor } from '../modules/event_processor'; import { OdpManager } from '../core/odp/odp_manager'; import { OdpConfig } from '../core/odp/odp_config'; +import { OdpEvent } from '../core/odp/odp_event'; +import { OptimizelySegmentOption } from '../core/odp/optimizely_segment_option'; import { UserAttributes, @@ -42,14 +44,14 @@ import { createDecisionService, DecisionService, DecisionObj } from '../core/dec import { getImpressionEvent, getConversionEvent } from '../core/event_builder'; import { buildImpressionEvent, buildConversionEvent } from '../core/event_builder/event_helpers'; import { NotificationRegistry } from '../core/notification_center/notification_registry'; -import fns from '../utils/fns' +import fns from '../utils/fns'; import { validate } from '../utils/attributes_validator'; -import * as enums from '../utils/enums'; import * as eventTagsValidator from '../utils/event_tags_validator'; import * as projectConfig from '../core/project_config'; import * as userProfileServiceValidator from '../utils/user_profile_service_validator'; import * as stringValidator from '../utils/string_value_validator'; import * as decision from '../core/decision'; + import { ERROR_MESSAGES, LOG_LEVEL, @@ -58,7 +60,11 @@ import { DECISION_MESSAGES, FEATURE_VARIABLE_TYPES, DECISION_NOTIFICATION_TYPES, - NOTIFICATION_TYPES + NOTIFICATION_TYPES, + NODE_CLIENT_ENGINE, + NODE_CLIENT_VERSION, + ODP_EVENT_ACTION, + ODP_DEFAULT_EVENT_TYPE, } from '../utils/enums'; const MODULE_NAME = 'OPTIMIZELY'; @@ -81,28 +87,23 @@ export default class Optimizely { private clientEngine: string; private clientVersion: string; private errorHandler: ErrorHandler; - private logger: LoggerFacade; + protected logger: LoggerFacade; private projectConfigManager: ProjectConfigManager; private decisionService: DecisionService; private eventProcessor: EventProcessor; private defaultDecideOptions: { [key: string]: boolean }; - private odpManager?: OdpManager; + protected odpManager?: OdpManager; public notificationCenter: NotificationCenter; constructor(config: OptimizelyOptions) { let clientEngine = config.clientEngine; if (!clientEngine) { - config.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.INVALID_CLIENT_ENGINE, - MODULE_NAME, - clientEngine, - ); - clientEngine = enums.NODE_CLIENT_ENGINE; + config.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine); + clientEngine = NODE_CLIENT_ENGINE; } this.clientEngine = clientEngine; - this.clientVersion = config.clientVersion || enums.NODE_CLIENT_VERSION; + this.clientVersion = config.clientVersion || NODE_CLIENT_VERSION; this.errorHandler = config.errorHandler; this.isOptimizelyConfigValid = config.isValidInstance; this.logger = config.logger; @@ -114,17 +115,12 @@ export default class Optimizely { } const defaultDecideOptions: { [key: string]: boolean } = {}; - decideOptionsArray.forEach((option) => { + decideOptionsArray.forEach(option => { // Filter out all provided default decide options that are not in OptimizelyDecideOption[] if (OptimizelyDecideOption[option]) { defaultDecideOptions[option] = true; } else { - this.logger.log( - LOG_LEVEL.WARNING, - LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, - MODULE_NAME, - option, - ); + this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); } }); this.defaultDecideOptions = defaultDecideOptions; @@ -132,21 +128,19 @@ export default class Optimizely { datafile: config.datafile, jsonSchemaValidator: config.jsonSchemaValidator, sdkKey: config.sdkKey, - datafileManager: config.datafileManager + datafileManager: config.datafileManager, }); - this.disposeOnUpdate = this.projectConfigManager.onUpdate( - (configObj: projectConfig.ProjectConfig) => { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, - MODULE_NAME, - configObj.revision, - configObj.projectId, - ); - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); - } - ); + this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { + this.logger.log( + LOG_LEVEL.INFO, + LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, + MODULE_NAME, + configObj.revision, + configObj.projectId + ); + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + }); const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); @@ -157,7 +151,7 @@ export default class Optimizely { userProfileService = config.userProfileService; this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_USER_PROFILE_SERVICE, MODULE_NAME); } - } catch (ex: any) { + } catch (ex) { this.logger.log(LOG_LEVEL.WARNING, ex.message); } } @@ -174,27 +168,30 @@ export default class Optimizely { const eventProcessorStartedPromise = this.eventProcessor.start(); - this.readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then((promiseResults) => { - if (config.odpManager != null) { - this.odpManager = config.odpManager; - this.odpManager.eventManager?.start(); - this.updateODPSettings(); - const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; - if (sdkKey != null) { - NotificationRegistry.getNotificationCenter(sdkKey, this.logger) - ?.addNotificationListener(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, () => this.updateODPSettings()); - } else { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE); + this.readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then( + promiseResults => { + if (config.odpManager != null) { + this.odpManager = config.odpManager; + this.odpManager.eventManager?.start(); + this.updateOdpSettings(); + const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; + if (sdkKey != null) { + NotificationRegistry.getNotificationCenter( + sdkKey, + this.logger + )?.addNotificationListener(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, () => this.updateOdpSettings()); + } else { + this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE); + } } - } - // Only return status from project config promise because event processor promise does not return any status. - return promiseResults[0]; - }) + // Only return status from project config promise because event processor promise does not return any status. + return promiseResults[0]; + } + ); this.readyTimeouts = {}; this.nextReadyTimeoutId = 0; - } /** @@ -238,12 +235,7 @@ export default class Optimizely { // If experiment is not set to 'Running' status, log accordingly and return variation key if (!projectConfig.isRunning(configObj, experimentKey)) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.SHOULD_NOT_DISPATCH_ACTIVATE, - MODULE_NAME, - experimentKey, - ); + this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.SHOULD_NOT_DISPATCH_ACTIVATE, MODULE_NAME, experimentKey); return variationKey; } @@ -252,30 +244,18 @@ export default class Optimizely { const decisionObj = { experiment: experiment, variation: variation, - decisionSource: enums.DECISION_SOURCES.EXPERIMENT - } + decisionSource: DECISION_SOURCES.EXPERIMENT, + }; - this.sendImpressionEvent( - decisionObj, - '', - userId, - true, - attributes - ); + this.sendImpressionEvent(decisionObj, '', userId, true, attributes); return variationKey; - } catch (ex: any) { + } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, - MODULE_NAME, - userId, - experimentKey, - ); + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); this.errorHandler.handleError(ex); return null; } - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -297,7 +277,7 @@ export default class Optimizely { flagKey: string, userId: string, enabled: boolean, - attributes?: UserAttributes, + attributes?: UserAttributes ): void { const configObj = this.projectConfigManager.getConfig(); if (!configObj) { @@ -402,12 +382,7 @@ export default class Optimizely { } if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { - this.logger.log( - LOG_LEVEL.WARNING, - enums.LOG_MESSAGES.EVENT_KEY_NOT_FOUND, - MODULE_NAME, - eventKey, - ); + this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.EVENT_KEY_NOT_FOUND, MODULE_NAME, eventKey); this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); return; } @@ -423,11 +398,11 @@ export default class Optimizely { clientVersion: this.clientVersion, configObj: configObj, }); - this.logger.log(LOG_LEVEL.INFO, enums.LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId); + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId); // TODO is it okay to not pass a projectConfig as second argument this.eventProcessor.process(conversionEvent); this.emitNotificationCenterTrack(eventKey, userId, attributes, eventTags); - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); @@ -440,7 +415,12 @@ export default class Optimizely { * @param {UserAttributes} attributes * @param {EventTags} eventTags Values associated with the event. */ - private emitNotificationCenterTrack(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void { + private emitNotificationCenterTrack( + eventKey: string, + userId: string, + attributes?: UserAttributes, + eventTags?: EventTags + ): void { try { const configObj = this.projectConfigManager.getConfig(); if (!configObj) { @@ -466,7 +446,7 @@ export default class Optimizely { eventTags: eventTags, logEvent: conversionEvent, }); - } catch (ex: any) { + } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); this.errorHandler.handleError(ex); } @@ -498,19 +478,14 @@ export default class Optimizely { const experiment = configObj.experimentKeyMap[experimentKey]; if (!experiment) { - this.logger.log( - LOG_LEVEL.DEBUG, - ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, - MODULE_NAME, - experimentKey, - ); + this.logger.log(LOG_LEVEL.DEBUG, ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey); return null; } const variationKey = this.decisionService.getVariation( configObj, experiment, - this.createUserContext(userId, attributes) as OptimizelyUserContext + this.createInternalUserContext(userId, attributes) as OptimizelyUserContext ).result; const decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) ? DECISION_NOTIFICATION_TYPES.FEATURE_TEST @@ -527,12 +502,12 @@ export default class Optimizely { }); return variationKey; - } catch (ex: any) { + } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); this.errorHandler.handleError(ex); return null; } - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -559,7 +534,7 @@ export default class Optimizely { try { return this.decisionService.setForcedVariation(configObj, experimentKey, userId, variationKey); - } catch (ex: any) { + } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); this.errorHandler.handleError(ex); return false; @@ -584,7 +559,7 @@ export default class Optimizely { try { return this.decisionService.getForcedVariation(configObj, experimentKey, userId).result; - } catch (ex: any) { + } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); this.errorHandler.handleError(ex); return null; @@ -599,11 +574,7 @@ export default class Optimizely { * @return {boolean} True if inputs are valid * */ - private validateInputs( - stringInputs: StringInputs, - userAttributes?: unknown, - eventTags?: unknown - ): boolean { + protected validateInputs(stringInputs: StringInputs, userAttributes?: unknown, eventTags?: unknown): boolean { try { if (stringInputs.hasOwnProperty('user_id')) { const userId = stringInputs['user_id']; @@ -617,7 +588,7 @@ export default class Optimizely { if (!stringValidator.validate(stringInputs[key as InputKey])) { throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, key)); } - }) + }); if (userAttributes) { validate(userAttributes); } @@ -625,13 +596,11 @@ export default class Optimizely { eventTagsValidator.validate(eventTags); } return true; - - } catch (ex: any) { + } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); this.errorHandler.handleError(ex); return false; } - } /** @@ -641,13 +610,7 @@ export default class Optimizely { * @return {null} */ private notActivatingExperiment(experimentKey: string, userId: string): null { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, - MODULE_NAME, - userId, - experimentKey, - ); + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); return null; } @@ -675,12 +638,7 @@ export default class Optimizely { isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean { try { if (!this.isValidInstance()) { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.INVALID_OBJECT, - MODULE_NAME, - 'isFeatureEnabled', - ); + this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'isFeatureEnabled'); return false; } @@ -699,7 +657,7 @@ export default class Optimizely { } let sourceInfo = {}; - const user = this.createUserContext(userId, attributes) as OptimizelyUserContext; + const user = this.createInternalUserContext(userId, attributes) as OptimizelyUserContext; const decisionObj = this.decisionService.getVariationForFeature(configObj, feature, user).result; const decisionSource = decisionObj.decisionSource; const experimentKey = decision.getExperimentKey(decisionObj); @@ -716,33 +674,15 @@ export default class Optimizely { if ( decisionSource === DECISION_SOURCES.FEATURE_TEST || - decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj) + (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)) ) { - this.sendImpressionEvent( - decisionObj, - feature.key, - userId, - featureEnabled, - attributes - ); + this.sendImpressionEvent(decisionObj, feature.key, userId, featureEnabled, attributes); } if (featureEnabled === true) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, - MODULE_NAME, - featureKey, - userId, - ); + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, - MODULE_NAME, - featureKey, - userId, - ); + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); featureEnabled = false; } @@ -761,7 +701,7 @@ export default class Optimizely { }); return featureEnabled; - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return false; @@ -779,12 +719,7 @@ export default class Optimizely { try { const enabledFeatures: string[] = []; if (!this.isValidInstance()) { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.INVALID_OBJECT, - MODULE_NAME, - 'getEnabledFeatures', - ); + this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getEnabledFeatures'); return enabledFeatures; } @@ -797,16 +732,14 @@ export default class Optimizely { return enabledFeatures; } - objectValues(configObj.featureKeyMap).forEach( - (feature: FeatureFlag) => { - if (this.isFeatureEnabled(feature.key, userId, attributes)) { - enabledFeatures.push(feature.key); - } + objectValues(configObj.featureKeyMap).forEach((feature: FeatureFlag) => { + if (this.isFeatureEnabled(feature.key, userId, attributes)) { + enabledFeatures.push(feature.key); } - ); + }); return enabledFeatures; - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return []; @@ -827,19 +760,14 @@ export default class Optimizely { * type, or null if the feature key is invalid or * the variable key is invalid */ - getFeatureVariable( - featureKey: string, - variableKey: string, - userId: string, - attributes?: UserAttributes - ): unknown { + getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): unknown { try { if (!this.isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, null, userId, attributes); - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -873,7 +801,8 @@ export default class Optimizely { variableKey: string, variableType: string | null, userId: string, - attributes?: UserAttributes): unknown { + attributes?: UserAttributes + ): unknown { if (!this.validateInputs({ feature_key: featureKey, variable_key: variableKey, user_id: userId }, attributes)) { return null; } @@ -899,15 +828,21 @@ export default class Optimizely { LOG_MESSAGES.VARIABLE_REQUESTED_WITH_WRONG_TYPE, MODULE_NAME, variableType, - variable.type, + variable.type ); return null; } - const user = this.createUserContext(userId, attributes) as OptimizelyUserContext; + const user = this.createInternalUserContext(userId, attributes) as OptimizelyUserContext; const decisionObj = this.decisionService.getVariationForFeature(configObj, featureFlag, user).result; const featureEnabled = decision.getFeatureEnabledFromVariation(decisionObj); - const variableValue = this.getFeatureVariableValueFromVariation(featureKey, featureEnabled, decisionObj.variation, variable, userId); + const variableValue = this.getFeatureVariableValueFromVariation( + featureKey, + featureEnabled, + decisionObj.variation, + variable, + userId + ); let sourceInfo = {}; if ( decisionObj.decisionSource === DECISION_SOURCES.FEATURE_TEST && @@ -977,7 +912,7 @@ export default class Optimizely { MODULE_NAME, variableValue, variable.key, - featureKey, + featureKey ); } else { this.logger.log( @@ -986,7 +921,7 @@ export default class Optimizely { MODULE_NAME, featureKey, userId, - variableValue, + variableValue ); } } else { @@ -995,7 +930,7 @@ export default class Optimizely { LOG_MESSAGES.VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, MODULE_NAME, variable.key, - variation.key, + variation.key ); } } else { @@ -1005,7 +940,7 @@ export default class Optimizely { MODULE_NAME, userId, variable.key, - featureKey, + featureKey ); } @@ -1036,8 +971,14 @@ export default class Optimizely { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.BOOLEAN, userId, attributes) as boolean | null; - } catch (e: any) { + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.BOOLEAN, + userId, + attributes + ) as boolean | null; + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -1069,8 +1010,14 @@ export default class Optimizely { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.DOUBLE, userId, attributes) as number | null; - } catch (e: any) { + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.DOUBLE, + userId, + attributes + ) as number | null; + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -1102,8 +1049,14 @@ export default class Optimizely { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.INTEGER, userId, attributes) as number | null; - } catch (e: any) { + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.INTEGER, + userId, + attributes + ) as number | null; + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -1135,8 +1088,14 @@ export default class Optimizely { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.STRING, userId, attributes) as string | null; - } catch (e: any) { + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.STRING, + userId, + attributes + ) as string | null; + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -1157,19 +1116,14 @@ export default class Optimizely { * invalid, or there is a mismatch with the type * of the variable */ - getFeatureVariableJSON( - featureKey: string, - variableKey: string, - userId: string, - attributes: UserAttributes - ): unknown { + getFeatureVariableJSON(featureKey: string, variableKey: string, userId: string, attributes: UserAttributes): unknown { try { if (!this.isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -1211,18 +1165,25 @@ export default class Optimizely { return null; } - const user = this.createUserContext(userId, attributes) as OptimizelyUserContext; + const user = this.createInternalUserContext(userId, attributes) as OptimizelyUserContext; const decisionObj = this.decisionService.getVariationForFeature(configObj, featureFlag, user).result; const featureEnabled = decision.getFeatureEnabledFromVariation(decisionObj); const allVariables: { [variableKey: string]: unknown } = {}; featureFlag.variables.forEach((variable: FeatureVariable) => { - allVariables[variable.key] = this.getFeatureVariableValueFromVariation(featureKey, featureEnabled, decisionObj.variation, variable, userId); + allVariables[variable.key] = this.getFeatureVariableValueFromVariation( + featureKey, + featureEnabled, + decisionObj.variation, + variable, + userId + ); }); let sourceInfo = {}; - if (decisionObj.decisionSource === DECISION_SOURCES.FEATURE_TEST && + if ( + decisionObj.decisionSource === DECISION_SOURCES.FEATURE_TEST && decisionObj.experiment !== null && decisionObj.variation !== null ) { @@ -1245,7 +1206,7 @@ export default class Optimizely { }); return allVariables; - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -1295,7 +1256,7 @@ export default class Optimizely { return null; } return this.projectConfigManager.getOptimizelyConfig(); - } catch (e: any) { + } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); return null; @@ -1335,6 +1296,10 @@ export default class Optimizely { */ close(): Promise<{ success: boolean; reason?: string }> { try { + if (this.odpManager) { + this.odpManager.close(); + } + this.notificationCenter.clearAllNotificationListeners(); const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; if (sdkKey) { @@ -1349,13 +1314,11 @@ export default class Optimizely { if (this.projectConfigManager) { this.projectConfigManager.stop(); } - Object.keys(this.readyTimeouts).forEach( - (readyTimeoutId: string) => { - const readyTimeoutRecord = this.readyTimeouts[readyTimeoutId]; - clearTimeout(readyTimeoutRecord.readyTimeout); - readyTimeoutRecord.onClose(); - } - ); + Object.keys(this.readyTimeouts).forEach((readyTimeoutId: string) => { + const readyTimeoutRecord = this.readyTimeouts[readyTimeoutId]; + clearTimeout(readyTimeoutRecord.readyTimeout); + readyTimeoutRecord.onClose(); + }); this.readyTimeouts = {}; return eventProcessorStoppedPromise.then( function() { @@ -1370,7 +1333,7 @@ export default class Optimizely { }; } ); - } catch (err: any) { + } catch (err) { this.logger.log(LOG_LEVEL.ERROR, err.message); this.errorHandler.handleError(err); return Promise.resolve({ @@ -1419,22 +1382,20 @@ export default class Optimizely { } let resolveTimeoutPromise: (value: OnReadyResult) => void; - const timeoutPromise = new Promise( - (resolve) => { - resolveTimeoutPromise = resolve; - } - ); + const timeoutPromise = new Promise(resolve => { + resolveTimeoutPromise = resolve; + }); const timeoutId = this.nextReadyTimeoutId; this.nextReadyTimeoutId++; - const onReadyTimeout = (() => { + const onReadyTimeout = () => { delete this.readyTimeouts[timeoutId]; resolveTimeoutPromise({ success: false, reason: sprintf('onReady timeout expired after %s ms', timeoutValue), }); - }); + }; const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); const onClose = function() { resolveTimeoutPromise({ @@ -1480,15 +1441,32 @@ export default class Optimizely { return new OptimizelyUserContext({ optimizely: this, userId, - attributes + attributes, + shouldIdentifyUser: true, }); } - decide( - user: OptimizelyUserContext, - key: string, - options: OptimizelyDecideOption[] = [] - ): OptimizelyDecision { + /** + * Creates an internal context of the user for which decision APIs will be called. + * + * A user context will be created successfully even when the SDK is not fully configured yet, so no + * this.isValidInstance() check is performed here. + * + * @param {string} userId The user ID to be used for bucketing. + * @param {UserAttributes} attributes Optional user attributes. + * @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or + * null if provided inputs are invalid + */ + private createInternalUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null { + return new OptimizelyUserContext({ + optimizely: this, + userId, + attributes, + shouldIdentifyUser: false, + }); + } + + decide(user: OptimizelyUserContext, key: string, options: OptimizelyDecideOption[] = []): OptimizelyDecision { const userId = user.getUserId(); const attributes = user.getAttributes(); const configObj = this.projectConfigManager.getConfig(); @@ -1514,15 +1492,10 @@ export default class Optimizely { decisionObj = { experiment: null, variation: variation, - decisionSource: DECISION_SOURCES.FEATURE_TEST - } + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }; } else { - const decisionVariation = this.decisionService.getVariationForFeature( - configObj, - feature, - user, - allDecideOptions, - ); + const decisionVariation = this.decisionService.getVariationForFeature(configObj, feature, user, allDecideOptions); reasons.push(...decisionVariation.reasons); decisionObj = decisionVariation.result; } @@ -1531,21 +1504,9 @@ export default class Optimizely { const variationKey = decisionObj.variation?.key ?? null; const flagEnabled: boolean = decision.getFeatureEnabledFromVariation(decisionObj); if (flagEnabled === true) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, - MODULE_NAME, - key, - userId, - ); + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, key, userId); } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, - MODULE_NAME, - key, - userId, - ); + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, key, userId); } const variablesMap: { [key: string]: unknown } = {}; @@ -1553,29 +1514,22 @@ export default class Optimizely { if (!allDecideOptions[OptimizelyDecideOption.EXCLUDE_VARIABLES]) { feature.variables.forEach(variable => { - variablesMap[variable.key] = - this.getFeatureVariableValueFromVariation( - key, - flagEnabled, - decisionObj.variation, - variable, - userId - ); + variablesMap[variable.key] = this.getFeatureVariableValueFromVariation( + key, + flagEnabled, + decisionObj.variation, + variable, + userId + ); }); } if ( - !allDecideOptions[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && ( - decisionSource === DECISION_SOURCES.FEATURE_TEST || - decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)) + !allDecideOptions[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && + (decisionSource === DECISION_SOURCES.FEATURE_TEST || + (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj))) ) { - this.sendImpressionEvent( - decisionObj, - key, - userId, - flagEnabled, - attributes - ) + this.sendImpressionEvent(decisionObj, key, userId, flagEnabled, attributes); decisionEventDispatched = true; } @@ -1583,7 +1537,7 @@ export default class Optimizely { let reportedReasons: string[] = []; if (shouldIncludeReasons) { - reportedReasons = reasons.map((reason) => sprintf(reason[0] as string, ...reason.slice(1))); + reportedReasons = reasons.map(reason => sprintf(reason[0] as string, ...reason.slice(1))); } const featureInfo = { @@ -1624,17 +1578,12 @@ export default class Optimizely { if (!Array.isArray(options)) { this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.INVALID_DECIDE_OPTIONS, MODULE_NAME); } else { - options.forEach((option) => { + options.forEach(option => { // Filter out all provided decide options that are not in OptimizelyDecideOption[] if (OptimizelyDecideOption[option]) { allDecideOptions[option] = true; } else { - this.logger.log( - LOG_LEVEL.WARNING, - LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, - MODULE_NAME, - option, - ); + this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); } }); } @@ -1701,7 +1650,7 @@ export default class Optimizely { /** * Updates ODP Config with most recent ODP key, host, and segments from the project config */ - updateODPSettings(): void { + private updateOdpSettings(): void { const projectConfig = this.projectConfigManager.getConfig(); if (this.odpManager != null && projectConfig != null) { this.odpManager.updateSettings( @@ -1709,4 +1658,66 @@ export default class Optimizely { ); } } + + /** + * Sends an action as an ODP Event with optional custom parameters including type, identifiers, and data + * Note: Since this depends on this.odpManager, it must await Optimizely client's onReady() promise resolution. + * @param {Object} odpEvent + * @param {ODP_EVENT_ACTION} odpEvent.action Subcategory of the event type (i.e. "client_initialized", or "") + * @param {string} odpEvent.type (Optional) Type of event (Defaults to "fullstack") + * @param {Map} odpEvent.identifiers (Optional) Key-value map of user identifiers + * @param {Map} odpEvent.data (Optional) Event data in a key-value map. + */ + public sendOdpEvent({ + type, + action, + identifiers, + data, + }: { + type?: string; + action: ODP_EVENT_ACTION; + identifiers?: Map; + data?: Map; + }): void { + if (!this.odpManager) { + this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING); + } + + const odpEventType = type ?? ODP_DEFAULT_EVENT_TYPE; + + try { + const odpEvent = new OdpEvent(odpEventType, action, identifiers, data); + this.odpManager!.sendEvent(odpEvent); + } catch (e) { + this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED, e); + } + } + + /** + * Identifies user with ODP server in a fire-and-forget manner. + * @param {string} userId + */ + public identifyUser(userId: string): void { + if (this.odpManager && this.odpManager.enabled) { + this.odpManager.identifyUser(userId); + } + } + + /** + * Fetches list of qualified segments from ODP for a particular userId. + * @param {string} userId + * @param {Array} options + * @returns {Promise} + */ + public async fetchQualifiedSegments( + userId: string, + options?: Array + ): Promise { + if (!this.odpManager) { + this.logger.error(ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING); + return null; + } + + return await this.odpManager.fetchQualifiedSegments(userId, options); + } } diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js index 3e7f9a746..239580a14 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2020-2022, Optimizely, Inc. and contributors * + * Copyright 2020-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -38,17 +38,18 @@ describe('lib/optimizely_user_context', function() { var options = 'fakeOption'; describe('#setAttribute', function() { fakeOptimizely = { - decide: sinon.stub().returns({}) - } + decide: sinon.stub().returns({}), + }; it('should set attributes when provided at instantiation of OptimizelyUserContext', function() { var userId = 'user1'; var attributes = { test_attribute: 'test_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, - attributes + attributes, }); - user.setAttribute('k1', { 'hello': 'there' }); + user.setAttribute('k1', { hello: 'there' }); user.setAttribute('k2', true); user.setAttribute('k3', 100); user.setAttribute('k4', 3.5); @@ -57,7 +58,7 @@ describe('lib/optimizely_user_context', function() { var newAttributes = user.getAttributes(); assert.deepEqual(newAttributes['test_attribute'], 'test_value'); - assert.deepEqual(newAttributes['k1'], { 'hello': 'there' }); + assert.deepEqual(newAttributes['k1'], { hello: 'there' }); assert.deepEqual(newAttributes['k2'], true); assert.deepEqual(newAttributes['k3'], 100); assert.deepEqual(newAttributes['k4'], 3.5); @@ -66,10 +67,11 @@ describe('lib/optimizely_user_context', function() { it('should set attributes when none provided at instantiation of OptimizelyUserContext', function() { var userId = 'user2'; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); - user.setAttribute('k1', { 'hello': 'there' }); + user.setAttribute('k1', { hello: 'there' }); user.setAttribute('k2', true); user.setAttribute('k3', 100); user.setAttribute('k4', 3.5); @@ -77,7 +79,7 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(user.getUserId(), userId); var newAttributes = user.getAttributes(); - assert.deepEqual(newAttributes['k1'], { 'hello': 'there' }); + assert.deepEqual(newAttributes['k1'], { hello: 'there' }); assert.deepEqual(newAttributes['k2'], true); assert.deepEqual(newAttributes['k3'], 100); assert.deepEqual(newAttributes['k4'], 3.5); @@ -87,17 +89,18 @@ describe('lib/optimizely_user_context', function() { var userId = 'user3'; var attributes = { test_attribute: 'test_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, attributes, }); - user.setAttribute('k1', { 'hello': 'there' }); + user.setAttribute('k1', { hello: 'there' }); user.setAttribute('test_attribute', 'overwritten_value'); assert.deepEqual(user.getOptimizely(), fakeOptimizely); assert.deepEqual(user.getUserId(), userId); var newAttributes = user.getAttributes(); - assert.deepEqual(newAttributes['k1'], { 'hello': 'there' }); + assert.deepEqual(newAttributes['k1'], { hello: 'there' }); assert.deepEqual(newAttributes['test_attribute'], 'overwritten_value'); assert.deepEqual(Object.keys(newAttributes).length, 2); }); @@ -105,6 +108,7 @@ describe('lib/optimizely_user_context', function() { it('should allow to set attributes with value of null', function() { var userId = 'user4'; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); @@ -120,31 +124,33 @@ describe('lib/optimizely_user_context', function() { var userId = 'user1'; var attributes = { initial_attribute: 'initial_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, attributes, }); - attributes["attribute2"] = 100; + attributes['attribute2'] = 100; assert.deepEqual(user.getAttributes(), { initial_attribute: 'initial_value' }); - user.setAttribute("attribute3", "hello"); - assert.deepEqual(attributes, { initial_attribute: 'initial_value', 'attribute2': 100 }); + user.setAttribute('attribute3', 'hello'); + assert.deepEqual(attributes, { initial_attribute: 'initial_value', attribute2: 100 }); }); it('should not change user attributes if returned by getAttributes object is updated', function() { var userId = 'user1'; var attributes = { initial_attribute: 'initial_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, attributes, }); var attributes2 = user.getAttributes(); - attributes2['new_attribute'] = { "value": 100 }; + attributes2['new_attribute'] = { value: 100 }; assert.deepEqual(user.getAttributes(), attributes); var expectedAttributes = { initial_attribute: 'initial_value', - new_attribute: { "value": 100 } - } + new_attribute: { value: 100 }, + }; assert.deepEqual(attributes2, expectedAttributes); }); }); @@ -162,30 +168,25 @@ describe('lib/optimizely_user_context', function() { reasons: [], }; fakeOptimizely = { - decide: sinon.stub().returns(fakeDecision) + decide: sinon.stub().returns(fakeDecision), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var decision = user.decide(flagKey, options); - sinon.assert.calledWithExactly( - fakeOptimizely.decide, - user, - flagKey, - options - ); + sinon.assert.calledWithExactly(fakeOptimizely.decide, user, flagKey, options); assert.deepEqual(decision, fakeDecision); }); - }); + }); describe('#decideForKeys', function() { it('should return an expected decision results object', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; var fakeDecisionMap = { - flagKey1: - { + flagKey1: { variationKey: '18257766532', enabled: true, variables: {}, @@ -194,8 +195,7 @@ describe('lib/optimizely_user_context', function() { userContext: 'fakeUserContext', reasons: [], }, - flagKey2: - { + flagKey2: { variationKey: 'variation_with_traffic', enabled: true, variables: {}, @@ -206,19 +206,15 @@ describe('lib/optimizely_user_context', function() { }, }; fakeOptimizely = { - decideForKeys: sinon.stub().returns(fakeDecisionMap) + decideForKeys: sinon.stub().returns(fakeDecisionMap), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var decisionMap = user.decideForKeys([flagKey1, flagKey2], options); - sinon.assert.calledWithExactly( - fakeOptimizely.decideForKeys, - user, - [flagKey1, flagKey2], - options - ); + sinon.assert.calledWithExactly(fakeOptimizely.decideForKeys, user, [flagKey1, flagKey2], options); assert.deepEqual(decisionMap, fakeDecisionMap); }); }); @@ -229,8 +225,7 @@ describe('lib/optimizely_user_context', function() { var flagKey2 = 'feature_2'; var flagKey3 = 'feature_3'; var fakeDecisionMap = { - flagKey1: - { + flagKey1: { variationKey: '18257766532', enabled: true, variables: {}, @@ -239,8 +234,7 @@ describe('lib/optimizely_user_context', function() { userContext: 'fakeUserContext', reasons: [], }, - flagKey2: - { + flagKey2: { variationKey: 'variation_with_traffic', enabled: true, variables: {}, @@ -249,8 +243,7 @@ describe('lib/optimizely_user_context', function() { userContext: 'fakeUserContext', reasons: [], }, - flagKey3: - { + flagKey3: { variationKey: '', enabled: false, variables: {}, @@ -261,18 +254,15 @@ describe('lib/optimizely_user_context', function() { }, }; fakeOptimizely = { - decideAll: sinon.stub().returns(fakeDecisionMap) + decideAll: sinon.stub().returns(fakeDecisionMap), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var decisionMap = user.decideAll(options); - sinon.assert.calledWithExactly( - fakeOptimizely.decideAll, - user, - options - ); + sinon.assert.calledWithExactly(fakeOptimizely.decideAll, user, options); assert.deepEqual(decisionMap, fakeDecisionMap); }); }); @@ -280,11 +270,12 @@ describe('lib/optimizely_user_context', function() { describe('#trackEvent', function() { it('should call track from optimizely client', function() { fakeOptimizely = { - track: sinon.stub() + track: sinon.stub(), }; var eventName = 'myEvent'; - var eventTags = { 'eventTag1': 1000 } + var eventTags = { eventTag1: 1000 }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); @@ -294,7 +285,7 @@ describe('lib/optimizely_user_context', function() { eventName, user.getUserId(), user.getAttributes(), - eventTags, + eventTags ); }); }); @@ -317,41 +308,41 @@ describe('lib/optimizely_user_context', function() { }); it('should return true when client is not ready', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(false) + isValidInstance: sinon.stub().returns(false), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var result = user.setForcedDecision({ flagKey: 'feature_1' }, '3324490562'); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); it('should return true when provided empty string flagKey', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); var result = user.setForcedDecision({ flagKey: '' }, '3324490562'); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); it('should return true when provided flagKey and variationKey', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); var result = user.setForcedDecision({ flagKey: 'feature_1' }, '3324490562'); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); describe('when valid forced decision is set', function() { @@ -373,7 +364,7 @@ describe('lib/optimizely_user_context', function() { notificationCenter, }); - sinon.stub(optlyInstance.decisionService.logger, 'log') + sinon.stub(optlyInstance.decisionService.logger, 'log'); sinon.stub(eventDispatcher, 'dispatchEvent'); sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); }); @@ -388,11 +379,11 @@ describe('lib/optimizely_user_context', function() { var flagKey = 'feature_1'; var ruleKey = 'exp_with_audience'; var variationKey = '3324490633'; - + var user = optlyInstance.createUserContext(userId); user.setForcedDecision({ flagKey: flagKey, ruleKey }, { variationKey }); var decision = user.decide(flagKey, options); - + assert.equal(decision.variationKey, variationKey); assert.equal(decision.ruleKey, ruleKey); assert.equal(decision.enabled, true); @@ -406,29 +397,24 @@ describe('lib/optimizely_user_context', function() { var featureKey = 'feature_1'; var variationKey = '3324490562'; user.setForcedDecision({ flagKey: featureKey }, { variationKey }); - var decision = user.decide( - featureKey, - [ - OptimizelyDecideOption.INCLUDE_REASONS, - OptimizelyDecideOption.DISABLE_DECISION_EVENT - ] - ); + var decision = user.decide(featureKey, [ + OptimizelyDecideOption.INCLUDE_REASONS, + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + ]); assert.equal(decision.variationKey, variationKey); assert.equal(decision.ruleKey, null); assert.equal(decision.enabled, true); assert.equal(decision.userContext.getUserId(), userId); assert.deepEqual(decision.userContext.getAttributes(), {}); assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); - assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], { variationKey }); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); assert.equal( true, decision.reasons.includes( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, - variationKey, - featureKey, - userId, - ) + sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) ) ); @@ -448,16 +434,14 @@ describe('lib/optimizely_user_context', function() { assert.equal(decision.userContext.getUserId(), userId); assert.deepEqual(decision.userContext.getAttributes(), {}); assert.deepEqual(Object.values(decision.userContext.forcedDecisionsMap).length, 1); - assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], { variationKey }); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); assert.equal( true, decision.reasons.includes( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, - variationKey, - featureKey, - userId, - ) + sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) ) ); @@ -503,12 +487,12 @@ describe('lib/optimizely_user_context', function() { LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, - userId, - ) + userId + ), ], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -537,7 +521,7 @@ describe('lib/optimizely_user_context', function() { variationKey, featureKey, ruleKey, - userId, + userId ) ) ); @@ -580,18 +564,17 @@ describe('lib/optimizely_user_context', function() { }, decisionEventDispatched: true, reasons: [ - sprintf( LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, - userId, - ) + userId + ), ], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); @@ -615,7 +598,10 @@ describe('lib/optimizely_user_context', function() { sinon.assert.called(stubLogHandler.log); var logMessage = optlyInstance.decisionService.logger.log.args[4]; assert.strictEqual(logMessage[0], 2); - assert.strictEqual(logMessage[1], 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'); + assert.strictEqual( + logMessage[1], + 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.' + ); assert.strictEqual(logMessage[2], variationKey); assert.strictEqual(logMessage[3], featureKey); assert.strictEqual(logMessage[4], ruleKey); @@ -660,8 +646,8 @@ describe('lib/optimizely_user_context', function() { decisionEventDispatched: true, reasons: [], }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); @@ -697,15 +683,14 @@ describe('lib/optimizely_user_context', function() { assert.equal(decision.variationKey, '18257766532'); assert.equal(decision.ruleKey, '18322080788'); assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); - assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], { variationKey }); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); assert.equal( true, decision.reasons.includes( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, - featureKey, - userId, - ) + sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, featureKey, userId) ) ); }); @@ -731,7 +716,7 @@ describe('lib/optimizely_user_context', function() { LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, - userId, + userId ) ) ); @@ -758,7 +743,7 @@ describe('lib/optimizely_user_context', function() { LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, - userId, + userId ) ) ); @@ -865,9 +850,10 @@ describe('lib/optimizely_user_context', function() { it('should return true when client is not ready and the forced decision has been removed successfully', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(false) + isValidInstance: sinon.stub().returns(false), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); @@ -878,9 +864,10 @@ describe('lib/optimizely_user_context', function() { it('should return true when the forced decision has been removed successfully and false otherwise', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); @@ -890,15 +877,14 @@ describe('lib/optimizely_user_context', function() { var result2 = user.removeForcedDecision('non-existent_feature'); assert.strictEqual(result2, false); - - sinon.assert.notCalled(stubLogHandler.log); }); it('should successfully remove forced decision when multiple forced decisions set with same feature key', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); @@ -939,22 +925,23 @@ describe('lib/optimizely_user_context', function() { it('should return true when client is not ready', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(false) + isValidInstance: sinon.stub().returns(false), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var result = user.removeAllForcedDecisions(); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); it('should return true when all forced decisions have been removed successfully', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); @@ -964,7 +951,9 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(Object.keys(user.forcedDecisionsMap['feature_1']).length, 2); assert.deepEqual(user.getForcedDecision({ flagKey: 'feature_1' }), { variationKey: '3324490562' }); - assert.deepEqual(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }), { variationKey: 'b' }); + assert.deepEqual(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }), { + variationKey: 'b', + }); var result1 = user.removeAllForcedDecisions(); assert.strictEqual(result1, true); @@ -972,8 +961,42 @@ describe('lib/optimizely_user_context', function() { assert.strictEqual(user.getForcedDecision({ flagKey: 'feature_1' }), null); assert.strictEqual(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }), null); + }); + }); + + describe('fetchQualifiedSegments', () => { + it('should successfully call fetch qualified segments', async () => { + fakeOptimizely = { + fetchQualifiedSegments: sinon.stub().returns(true), + }; + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + const fetchedQualifiedSegments = await user.fetchQualifiedSegments(); + assert.deepEqual(fetchedQualifiedSegments, true); + + sinon.assert.calledWithExactly(fakeOptimizely.fetchQualifiedSegments, userId, undefined); + + assert.deepEqual(user.qualifiedSegments, []); + }); + }); + + describe('isQualifiedFor', () => { + it('should successfully return the expected result for if a user is qualified for a particular segment or not', () => { + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + user.qualifiedSegments = ['a', 'b']; - sinon.assert.notCalled(stubLogHandler.log); + assert.deepEqual(user.isQualifiedFor('a'), true); + assert.deepEqual(user.isQualifiedFor('b'), true); + assert.deepEqual(user.isQualifiedFor('c'), false); }); }); }); diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts index 83bca5f97..09936b40b 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2020-2022, Optimizely, Inc. and contributors * + * Copyright 2020-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -23,6 +23,14 @@ import { UserAttributes, } from '../../lib/shared_types'; import { CONTROL_ATTRIBUTES } from '../utils/enums'; +import { OptimizelySegmentOption } from '../core/odp/optimizely_segment_option'; + +interface OptimizelyUserContextConfig { + optimizely: Optimizely; + userId: string; + attributes?: UserAttributes; + shouldIdentifyUser?: boolean; +} export default class OptimizelyUserContext { private optimizely: Optimizely; @@ -31,19 +39,23 @@ export default class OptimizelyUserContext { private forcedDecisionsMap: { [key: string]: { [key: string]: OptimizelyForcedDecision } }; private _qualifiedSegments: string[] = []; - constructor({ - optimizely, - userId, - attributes, - }: { - optimizely: Optimizely, - userId: string, - attributes?: UserAttributes, - }) { + constructor({ optimizely, userId, attributes, shouldIdentifyUser = true }: OptimizelyUserContextConfig) { this.optimizely = optimizely; this.userId = userId; this.attributes = { ...attributes } ?? {}; this.forcedDecisionsMap = {}; + + if (shouldIdentifyUser) { + this.identifyUser(); + } + } + + /** + * On user context instantiation, fire event to attempt to identify user to ODP. + * Note: This fails if ODP is not enabled. + */ + private identifyUser(): void { + this.optimizely.identifyUser(this.userId); } /** @@ -82,11 +94,7 @@ export default class OptimizelyUserContext { * @param {OptimizelyDecideOption} options An array of options for decision-making. * @return {OptimizelyDecision} A decision result. */ - decide( - key: string, - options: OptimizelyDecideOption[] = [] - ): OptimizelyDecision { - + decide(key: string, options: OptimizelyDecideOption[] = []): OptimizelyDecision { return this.optimizely.decide(this.cloneUserContext(), key, options); } @@ -98,11 +106,7 @@ export default class OptimizelyUserContext { * @param {OptimizelyDecideOption[]} options An array of options for decision-making. * @return {[key: string]: OptimizelyDecision} An object of decision results mapped by flag keys. */ - decideForKeys( - keys: string[], - options: OptimizelyDecideOption[] = [], - ): { [key: string]: OptimizelyDecision } { - + decideForKeys(keys: string[], options: OptimizelyDecideOption[] = []): { [key: string]: OptimizelyDecision } { return this.optimizely.decideForKeys(this.cloneUserContext(), keys, options); } @@ -111,10 +115,7 @@ export default class OptimizelyUserContext { * @param {OptimizelyDecideOption[]} options An array of options for decision-making. * @return {[key: string]: OptimizelyDecision} An object of all decision results mapped by flag keys. */ - decideAll( - options: OptimizelyDecideOption[] = [] - ): { [key: string]: OptimizelyDecision } { - + decideAll(options: OptimizelyDecideOption[] = []): { [key: string]: OptimizelyDecision } { return this.optimizely.decideAll(this.cloneUserContext(), options); } @@ -212,12 +213,9 @@ export default class OptimizelyUserContext { return null; } - public isQualifiedFor(segment: string): boolean { - return this._qualifiedSegments.indexOf(segment) > -1; - } - private cloneUserContext(): OptimizelyUserContext { const userContext = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: this.getOptimizely(), userId: this.getUserId(), attributes: this.getAttributes(), @@ -233,4 +231,27 @@ export default class OptimizelyUserContext { return userContext; } + + /** + * Fetches a target user's list of qualified segments filtered by any given segment options and stores in qualifiedSegments. + * @param {OptimizelySegmentOption[]} options (Optional) List of segment options used to filter qualified segment results. + * @returns Boolean representing if segments were populated. + */ + async fetchQualifiedSegments(options?: OptimizelySegmentOption[]): Promise { + const segments = await this.optimizely.fetchQualifiedSegments(this.userId, options); + if (segments) { + this.qualifiedSegments = [...segments]; + } + + return !!segments; + } + + /** + * Returns a boolean representing if a user is qualified for a particular segment. + * @param {string} segment Target segment to be evaluated for user qualification. + * @returns {boolean} Boolean representing if a user qualified for the passed in segment. + */ + isQualifiedFor(segment: string): boolean { + return this._qualifiedSegments.indexOf(segment) > -1; + } } diff --git a/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts b/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts index d65a9b4a8..48d64ca70 100644 --- a/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts +++ b/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,27 +14,62 @@ * limitations under the License. */ +import { tryWithLocalStorage } from '../../utils/local_storage/tryLocalStorage'; import PersistentKeyValueCache from './persistentKeyValueCache'; +import { getLogger } from '../../modules/logging'; +import { ERROR_MESSAGES } from './../../utils/enums/index'; export default class BrowserAsyncStorageCache implements PersistentKeyValueCache { + logger = getLogger(); + async contains(key: string): Promise { - return localStorage.getItem(key) !== null; + return tryWithLocalStorage({ + browserCallback: (localStorage?: Storage) => { + return localStorage?.getItem(key) !== null; + }, + nonBrowserCallback: () => { + this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); + return false; + }, + }); } - async get(key: string): Promise { - return localStorage.getItem(key); + async get(key: string): Promise { + return tryWithLocalStorage({ + browserCallback: (localStorage?: Storage) => { + return localStorage?.getItem(key); + }, + nonBrowserCallback: () => { + this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); + return null; + }, + }); } async remove(key: string): Promise { if (await this.contains(key)) { - localStorage.removeItem(key); - return true; - } else { - return false; - } + tryWithLocalStorage({ + browserCallback: (localStorage?: Storage) => { + localStorage?.removeItem(key); + }, + nonBrowserCallback: () => { + this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); + }, + }); + return true; + } else { + return false; + } } async set(key: string, val: string): Promise { - return localStorage.setItem(key, val); + return tryWithLocalStorage({ + browserCallback: (localStorage?: Storage) => { + localStorage?.setItem(key, val); + }, + nonBrowserCallback: () => { + this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); + }, + }); } } diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts index ae2bcfd70..d15b2dc2f 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts @@ -15,7 +15,7 @@ */ import { BROWSER_CLIENT_VERSION, ERROR_MESSAGES, JAVASCRIPT_CLIENT_ENGINE, ODP_USER_KEY } from '../../utils/enums'; -import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +import { getLogger, LoggerFacade, LogHandler, LogLevel } from '../../modules/logging'; import { BrowserRequestHandler } from './../../utils/http_request_handler/browser_request_handler'; import BrowserAsyncStorageCache from '../key_value_cache/browserAsyncStorageCache'; @@ -28,6 +28,7 @@ import { OdpManager } from '../../core/odp/odp_manager'; import { OdpEvent } from '../../core/odp/odp_event'; import { OdpEventManager } from '../../core/odp/odp_event_manager'; import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { OdpOptions } from '../../shared_types'; interface BrowserOdpManagerConfig { disable: boolean; @@ -105,6 +106,7 @@ export class BrowserOdpManager extends OdpManager { * @override * - Sends an event to the ODP Server via the ODP Events API * - Intercepts identifiers and injects VUID before sending event + * - Identifiers must contain at least one key-value pair * @param {OdpEvent} odpEvent > ODP Event to send to event manager */ public sendEvent({ type, action, identifiers, data }: OdpEvent): void { @@ -120,4 +122,29 @@ export class BrowserOdpManager extends OdpManager { super.sendEvent({ type, action, identifiers: identifiersWithVuid, data }); } + + public static createBrowserOdpManager({ + logger = getLogger(), + odpOptions, + }: { + logger: LoggerFacade; + odpOptions?: OdpOptions; + }): BrowserOdpManager { + if (!odpOptions) { + return new BrowserOdpManager({ disable: false, logger }); + } + + return new BrowserOdpManager({ + disable: odpOptions.disabled || false, + segmentsCache: + odpOptions?.segmentsCache || + new BrowserLRUCache({ + maxSize: odpOptions.segmentsCacheSize, + timeout: odpOptions.segmentsCacheTimeout, + }), + segmentManager: odpOptions.segmentManager, + eventManager: odpOptions.eventManager, + logger, + }); + } } diff --git a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts b/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts index 4518006c2..2b29ff9e4 100644 --- a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts +++ b/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts @@ -125,7 +125,7 @@ export class VuidManager implements IVuidManager { * @param vuid VistorId to check * @returns *true* if the VisitorId is valid otherwise *false* for invalid */ - static isVuid = (vuid: string): boolean => vuid.startsWith(VuidManager.vuid_prefix); + static isVuid = (vuid: string): boolean => vuid?.startsWith(VuidManager.vuid_prefix) || false; /** * Function used in unit testing to reset the VuidManager diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index 29122436d..377890c9c 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -15,10 +15,14 @@ */ import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from '../lib/modules/logging'; import { EventProcessor } from '../lib/modules/event_processor'; -import { OdpManager } from './core/odp/odp_manager'; import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; -import { NOTIFICATION_TYPES } from './utils/enums'; +import { NOTIFICATION_TYPES, ODP_EVENT_ACTION } from './utils/enums'; + +import { OdpManager } from './core/odp/odp_manager'; +import { OdpSegmentManager } from './core/odp/odp_segment_manager'; +import { LRUCache } from './utils/lru_cache'; +import { OdpEventManager } from './core/odp/odp_event_manager'; export interface BucketerParams { experimentId: string; @@ -75,6 +79,15 @@ export interface DatafileOptions { datafileAccessToken?: string; } +export interface OdpOptions { + disabled?: boolean; + segmentsCache?: LRUCache; + segmentsCacheSize?: number; + segmentsCacheTimeout?: number; + segmentManager?: OdpSegmentManager; + eventManager?: OdpEventManager; +} + export interface ListenerPayload { userId: string; attributes?: UserAttributes; @@ -284,6 +297,12 @@ export interface OptimizelyVariable { value: string; } +export interface BrowserClient extends Client { + getVuid(): string; + // TODO: In the future, will add a function to allow overriding the VUID. + createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null; +} + export interface Client { notificationCenter: NotificationCenter; createUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null; @@ -328,6 +347,12 @@ export interface Client { getOptimizelyConfig(): OptimizelyConfig | null; onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }>; close(): Promise<{ success: boolean; reason?: string }>; + sendOdpEvent(payload: { + type?: string; + action: ODP_EVENT_ACTION; + identifiers?: Map; + data?: Map; + }): void; } export interface ActivateListenerPayload extends ListenerPayload { @@ -352,7 +377,7 @@ export interface Config extends ConfigLite { eventFlushInterval?: number; // Maximum time for an event to be enqueued eventMaxQueueSize?: number; // Maximum size for the event queue sdkKey?: string; - odpManager?: OdpManager; + odpOptions?: OdpOptions; } /** @@ -465,6 +490,7 @@ export interface OptimizelyUserContext { getForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null; removeForcedDecision(context: OptimizelyDecisionContext): boolean; removeAllForcedDecisions(): boolean; + fetchQualifiedSegments(): Promise; isQualifiedFor(segment: string): boolean; } diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index 3911aac58..e31e48df9 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -26,6 +26,7 @@ export const LOG_LEVEL = { }; export const ERROR_MESSAGES = { + BROWSER_ODP_MANAGER_INITIALIZATION_FAILED: '%s: Error initializing Browser ODP Manager.', CONDITION_EVALUATOR_ERROR: '%s: Error evaluating audience condition of type %s: %s', DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', @@ -50,21 +51,28 @@ export const ERROR_MESSAGES = { INVALID_ROLLOUT_ID: '%s: Invalid rollout ID %s attached to feature %s', INVALID_USER_ID: '%s: Provided user ID is in an invalid format.', INVALID_USER_PROFILE_SERVICE: '%s: Provided user profile service instance is in an invalid format: %s.', + LOCAL_STORAGE_DOES_NOT_EXIST: 'Error accessing window localStorage.', NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', - ODP_EVENT_FAILED: '%s: ODP event send failed (invalid url)', + ODP_EVENT_FAILED: 'ODP event send failed.', ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING: '%s: ODP unable to fetch qualified segments (Segments Manager not initialized).', ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING: '%s: ODP identify event %s is not dispatched (Event Manager not instantiated).', ODP_INITIALIZATION_FAILED: '%s: ODP failed to initialize.', ODP_INVALID_DATA: '%s: ODP data is not valid', + ODP_EVENT_FAILED_ODP_MANAGER_MISSING: '%s: ODP Event failed to send. (ODP Manager not initialized).', + ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING: + '%s: ODP failed to Fetch Qualified Segments. (ODP Manager not initialized).', + ODP_IDENTIFY_USER_FAILED_ODP_MANAGER_MISSING: '%s: ODP failed to Identify User. (ODP Manager not initialized).', + ODP_IDENTIFY_USER_FAILED_USER_CONTEXT_INITIALIZATION: + '%s: ODP failed to Identify User. (Failed during User Context Initialization).', ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING: '%s: ODP Manager failed to update OdpConfig settings for internal event manager. (Event Manager not initialized).', ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING: '%s: ODP Manager failed to update OdpConfig settings for internal segments manager. (Segments Manager not initialized).', - ODP_NOT_ENABLED: '%s: ODP is not enabled', + ODP_NOT_ENABLED: 'ODP is not enabled', ODP_NOT_INTEGRATED: '%s: ODP is not integrated', ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING: '%s: ODP send event %s was not dispatched (Event Manager not instantiated).', @@ -111,6 +119,7 @@ export const LOG_MESSAGES = { NO_ROLLOUT_EXISTS: '%s: There is no rollout of feature %s.', NOT_ACTIVATING_USER: '%s: Not activating user %s for experiment %s.', NOT_TRACKING_USER: '%s: Not tracking user %s.', + ODP_DISABLED: 'ODP Disabled.', ODP_IDENTIFY_FAILED_ODP_DISABLED: '%s: ODP identify event for user %s is not dispatched (ODP disabled).', ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED: '%s: ODP identify event %s is not dispatched (ODP not integrated).', PARSED_REVENUE_VALUE: '%s: Parsed revenue value "%s" from event tags.', @@ -337,7 +346,7 @@ export enum ODP_USER_KEY { FS_USER_ID = 'fs_user_id', } -export const ODP_EVENT_TYPE = 'fullstack'; +export const ODP_DEFAULT_EVENT_TYPE = 'fullstack'; /** * ODP Event Action Options diff --git a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts b/packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts index 96c3dc485..fb164808e 100644 --- a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts +++ b/packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts @@ -28,7 +28,11 @@ const MODULE_NAME = 'JSON_SCHEMA_VALIDATOR'; * @param {boolean} shouldThrowOnError Should validation throw if invalid JSON object * @return {boolean} true if the given object is valid; throws or false if invalid */ -export function validate(jsonObject: unknown, validationSchema: JSONSchema4 = schema, shouldThrowOnError = true): boolean { +export function validate( + jsonObject: unknown, + validationSchema: JSONSchema4 = schema, + shouldThrowOnError = true +): boolean { const moduleTitle = `${MODULE_NAME} (${validationSchema.title})`; if (typeof jsonObject !== 'object' || jsonObject === null) { @@ -46,7 +50,7 @@ export function validate(jsonObject: unknown, validationSchema: JSONSchema4 = sc if (Array.isArray(result.errors)) { throw new Error( - sprintf(ERROR_MESSAGES.INVALID_DATAFILE, moduleTitle, result.errors[0].property, result.errors[0].message), + sprintf(ERROR_MESSAGES.INVALID_DATAFILE, moduleTitle, result.errors[0].property, result.errors[0].message) ); } diff --git a/packages/optimizely-sdk/lib/utils/local_storage/tryLocalStorage.ts b/packages/optimizely-sdk/lib/utils/local_storage/tryLocalStorage.ts new file mode 100644 index 000000000..252cbf8e7 --- /dev/null +++ b/packages/optimizely-sdk/lib/utils/local_storage/tryLocalStorage.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Checks to see if browser localStorage available. If so, runs and returns browserCallback. Otherwise, runs and returns nonBrowserCallback. + * @param {object} callbacks + * @param {[object.browserCallback]} callbacks.browserCallback + * @param {[object.nonBrowserCallback]} callbacks.nonBrowserCallback + * @returns + */ +export const tryWithLocalStorage = ({ + browserCallback, + nonBrowserCallback, +}: { + browserCallback: (localStorage?: Storage) => K; + nonBrowserCallback: () => K; +}): K => { + return typeof window !== 'undefined' ? browserCallback(window?.localStorage) : nonBrowserCallback(); +}; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts index f66ad86ba..343aa85cb 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts @@ -16,16 +16,21 @@ import LRUCache, { ISegmentsCacheConfig } from './lru_cache'; +export interface BrowserLRUCacheConfig { + maxSize?: number; + timeout?: number; +} + export const BrowserLRUCacheConfig: ISegmentsCacheConfig = { DEFAULT_CAPACITY: 100, DEFAULT_TIMEOUT_SECS: 600, }; export class BrowserLRUCache extends LRUCache { - constructor() { + constructor(config?: BrowserLRUCacheConfig) { super({ - maxSize: BrowserLRUCacheConfig.DEFAULT_CAPACITY, - timeout: BrowserLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, + maxSize: config?.maxSize || BrowserLRUCacheConfig.DEFAULT_CAPACITY, + timeout: config?.timeout || BrowserLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, }); } } diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts index dc4d4c94f..388ddfbd9 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts @@ -14,8 +14,14 @@ * limitations under the License. */ +import { getLogger } from '../../modules/logging'; import CacheElement from './cache_element'; +export interface LRUCacheConfig { + maxSize: number; + timeout: number; +} + /** * Least-Recently Used Cache (LRU Cache) Implementation with Generic Key-Value Pairs * Analogous to a Map that has a specified max size and a timeout per element. @@ -37,7 +43,12 @@ export class LRUCache { return this._timeout; } - constructor({ maxSize, timeout }: { maxSize: number; timeout: number }) { + constructor({ maxSize, timeout }: LRUCacheConfig) { + const logger = getLogger(); + + logger.debug(`Provisioning cache with maxSize of ${maxSize}`); + logger.debug(`Provisioning cache with timeout of ${timeout}`); + this._maxSize = maxSize; this._timeout = timeout; } diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts index a933832f5..ebcd5f9ea 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import LRUCache, { ISegmentsCacheConfig } from './lru_cache'; +import LRUCache, { ISegmentsCacheConfig, LRUCacheConfig } from './lru_cache'; export const ServerLRUCacheConfig: ISegmentsCacheConfig = { DEFAULT_CAPACITY: 10000, @@ -22,10 +22,10 @@ export const ServerLRUCacheConfig: ISegmentsCacheConfig = { }; export class ServerLRUCache extends LRUCache { - constructor() { + constructor(config?: LRUCacheConfig) { super({ - maxSize: ServerLRUCacheConfig.DEFAULT_CAPACITY, - timeout: ServerLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, + maxSize: config?.maxSize || ServerLRUCacheConfig.DEFAULT_CAPACITY, + timeout: config?.timeout || ServerLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, }); } } diff --git a/packages/optimizely-sdk/rollup.config.js b/packages/optimizely-sdk/rollup.config.js index 70d60c278..8a7887714 100644 --- a/packages/optimizely-sdk/rollup.config.js +++ b/packages/optimizely-sdk/rollup.config.js @@ -28,20 +28,13 @@ const typescriptPluginOptions = { './lib/**/*.tests.ts', './lib/**/*.umdtests.js', './lib/tests', - 'node_modules' - ], - include: [ - './lib/**/*.ts', - './lib/**/*.js', + 'node_modules', ], + include: ['./lib/**/*.ts', './lib/**/*.js'], }; -const cjsBundleFor = (platform) => ({ - plugins: [ - resolve(), - commonjs(), - typescript(typescriptPluginOptions), - ], +const cjsBundleFor = platform => ({ + plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})), input: `lib/index.${platform}.ts`, output: { @@ -53,7 +46,7 @@ const cjsBundleFor = (platform) => ({ }, }); -const esmBundleFor = (platform) => ({ +const esmBundleFor = platform => ({ ...cjsBundleFor(platform), output: [ { @@ -68,7 +61,7 @@ const esmBundleFor = (platform) => ({ sourcemap: true, }, ], -}) +}); const umdBundle = { plugins: [ @@ -76,6 +69,7 @@ const umdBundle = { commonjs({ namedExports: { '@optimizely/js-sdk-event-processor': ['LogTierV1EventProcessor', 'LocalStoragePendingEventsDispatcher'], + 'json-schema': ['validate'], }, }), typescript(typescriptPluginOptions), @@ -101,11 +95,7 @@ const umdBundle = { // A separate bundle for json schema validator. const jsonSchemaBundle = { - plugins: [ - resolve(), - commonjs(), - typescript(typescriptPluginOptions), - ], + plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], external: ['json-schema', 'uuid'], input: 'lib/utils/json_schema_validator/index.ts', output: { @@ -133,15 +123,15 @@ const bundles = { // --config-cjs will build all three cjs-* bundles // --config-umd will build only the umd bundle // --config-umd --config-json will build both umd and the json-schema bundles -export default (args) => { +export default args => { const patterns = Object.keys(args) - .filter((arg) => arg.startsWith('config-')) - .map((arg) => arg.replace(/config-/, '')); + .filter(arg => arg.startsWith('config-')) + .map(arg => arg.replace(/config-/, '')); // default to matching all bundles if (!patterns.length) patterns.push(/.*/); return Object.entries(bundles) - .filter(([name, config]) => patterns.some((pattern) => name.match(pattern))) + .filter(([name, config]) => patterns.some(pattern => name.match(pattern))) .map(([name, config]) => config); }; diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index ffa223b15..8a07463ae 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ODP_EVENT_ACTION, ODP_EVENT_TYPE } from './../lib/utils/enums/index'; +import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE } from './../lib/utils/enums/index'; import { OdpConfig } from '../lib/core/odp/odp_config'; import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; @@ -388,7 +388,7 @@ describe('OdpEventManager', () => { const events = JSON.parse(data as string); const event = events[0]; expect(event.type).toEqual('fullstack'); - expect(event.action).toEqual('client_initialized'); + expect(event.action).toEqual(ODP_EVENT_ACTION.INITIALIZED); expect(event.identifiers).toEqual({ vuid: vuid }); expect(event.data.idempotence_id.length).toBe(36); // uuid length expect(event.data.data_source_type).toEqual('sdk'); @@ -424,7 +424,7 @@ describe('OdpEventManager', () => { expect(method).toEqual('POST'); const events = JSON.parse(data as string); const event = events[0]; - expect(event.type).toEqual(ODP_EVENT_TYPE); + expect(event.type).toEqual(ODP_DEFAULT_EVENT_TYPE); expect(event.action).toEqual(ODP_EVENT_ACTION.IDENTIFIED); expect(event.identifiers).toEqual({ vuid: vuid, fs_user_id: fsUserId }); expect(event.data.idempotence_id.length).toBe(36); // uuid length diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts index 536d91085..bc63c9412 100644 --- a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -16,7 +16,7 @@ import { anything, capture, instance, mock, resetCalls, verify } from 'ts-mockito'; -import { LOG_MESSAGES } from './../lib/utils/enums/index'; +import { LOG_MESSAGES, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from './../lib/utils/enums/index'; import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; import { LogHandler, LogLevel } from '../lib/modules/logging'; @@ -30,6 +30,7 @@ import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; +import { OdpEvent } from '../lib/core/odp/odp_event'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -46,40 +47,48 @@ const vuidB = 'vuid_b'; const odpConfigB = new OdpConfig(keyB, hostB, segmentsB); describe('OdpManager', () => { + let odpConfig: OdpConfig; + let mockLogger: LogHandler; - let mockRequestHandler: RequestHandler; + let fakeLogger: LogHandler; - let odpConfig: OdpConfig; - let logger: LogHandler; - let requestHandler: RequestHandler; + let mockRequestHandler: RequestHandler; + let fakeRequestHandler: RequestHandler; let mockEventApiManager: OdpEventApiManager; + let fakeEventApiManager: OdpEventApiManager; + let mockEventManager: OdpEventManager; + let fakeEventManager: OdpEventManager; + let mockSegmentApiManager: OdpSegmentApiManager; + let fakeSegmentApiManager: OdpSegmentApiManager; + let mockSegmentManager: OdpSegmentManager; + let fakeSegmentManager: OdpSegmentManager; - let eventApiManager: OdpEventApiManager; - let eventManager: OdpEventManager; - let segmentApiManager: OdpSegmentApiManager; - let segmentManager: OdpSegmentManager; + let mockBrowserOdpManager: BrowserOdpManager; + let fakeBrowserOdpManager: BrowserOdpManager; beforeAll(() => { mockLogger = mock(); mockRequestHandler = mock(); odpConfig = new OdpConfig(); - logger = instance(mockLogger); - requestHandler = instance(mockRequestHandler); + fakeLogger = instance(mockLogger); + fakeRequestHandler = instance(mockRequestHandler); mockEventApiManager = mock(); mockEventManager = mock(); mockSegmentApiManager = mock(); mockSegmentManager = mock(); + mockBrowserOdpManager = mock(); - eventApiManager = instance(mockEventApiManager); - eventManager = instance(mockEventManager); - segmentApiManager = instance(mockSegmentApiManager); - segmentManager = instance(mockSegmentManager); + fakeEventApiManager = instance(mockEventApiManager); + fakeEventManager = instance(mockEventManager); + fakeSegmentApiManager = instance(mockSegmentApiManager); + fakeSegmentManager = instance(mockSegmentManager); + fakeBrowserOdpManager = instance(mockBrowserOdpManager); }); beforeEach(() => { @@ -93,8 +102,8 @@ describe('OdpManager', () => { const browserOdpManagerInstance = () => new BrowserOdpManager({ disable: false, - eventManager, - segmentManager, + eventManager: fakeEventManager, + segmentManager: fakeSegmentManager, }); it('should register VUID automatically on BrowserOdpManager initialization', async () => { @@ -104,9 +113,9 @@ describe('OdpManager', () => { }); it('should drop relevant calls when OdpManager is initialized with the disabled flag, except for VUID', async () => { - const browserOdpManager = new BrowserOdpManager({ disable: true, logger }); + const browserOdpManager = new BrowserOdpManager({ disable: true, logger: fakeLogger }); - verify(mockLogger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); browserOdpManager.updateSettings(new OdpConfig('valid', 'host', [])); expect(browserOdpManager.odpConfig).toBeUndefined; @@ -141,7 +150,7 @@ describe('OdpManager', () => { it('should use new settings in event manager when ODP Config is updated', async () => { const browserOdpManager = new BrowserOdpManager({ disable: false, - eventManager, + eventManager: fakeEventManager, }); expect(browserOdpManager.eventManager).toBeDefined(); @@ -178,7 +187,7 @@ describe('OdpManager', () => { it('should use new settings in segment manager when ODP Config is updated', () => { const browserOdpManager = new BrowserOdpManager({ disable: false, - segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), + segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), fakeSegmentApiManager), }); const didUpdateA = browserOdpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); @@ -216,4 +225,32 @@ describe('OdpManager', () => { }); expect(browserOdpManagerB.eventManager).not.toBe(null); }); + + it("should call event manager's sendEvent if ODP Event is valid", () => { + const browserOdpManager = new BrowserOdpManager({ + disable: false, + eventManager: fakeEventManager, + }); + + const odpConfig = new OdpConfig('key', 'host', []); + + browserOdpManager.updateSettings(odpConfig); + + // Test Valid OdpEvent - calls event manager with valid OdpEvent object + const validIdentifiers = new Map(); + validIdentifiers.set('vuid', vuidA); + + const validOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, validIdentifiers); + + browserOdpManager.sendEvent(validOdpEvent); + verify(mockEventManager.sendEvent(anything())).once(); + + // Test Invalid OdpEvents - logs error and short circuits + // Does not include `vuid` in identifiers does not have a local this.vuid populated in BrowserOdpManager + const invalidOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, undefined); + + expect(() => { + browserOdpManager.sendEvent(invalidOdpEvent); + }).toThrow(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING); + }); }); diff --git a/packages/optimizely-sdk/tests/odpManager.spec.ts b/packages/optimizely-sdk/tests/odpManager.spec.ts index 29b7e01e7..58c0da371 100644 --- a/packages/optimizely-sdk/tests/odpManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.spec.ts @@ -96,7 +96,7 @@ describe('OdpManager', () => { it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { const odpManager = new OdpManager({ disable: true, requestHandler, logger }); - verify(mockLogger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); odpManager.updateSettings(new OdpConfig('valid', 'host', [])); expect(odpManager.odpConfig).toBeUndefined; diff --git a/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts b/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts index d59f6fb69..a0326df6e 100644 --- a/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts @@ -133,7 +133,7 @@ describe('OdpSegmentApiManager', () => { const response = manager['toGraphQLJson'](USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); expect(response).toBe( - `{"query" : "query {customer"(${USER_KEY} : "${USER_VALUE}") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"] {edges {node {name state}}}}}"}` + `{"query" : "query {customer(${USER_KEY} : \\"${USER_VALUE}\\") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"]) {edges {node {name state}}}}}"}` ); }); From 198c46e51f5a0676383b6905b85979d0858355bc Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Fri, 17 Mar 2023 11:04:30 -0400 Subject: [PATCH 004/200] Refactor non-code-path resources from FS => FX (#803) --- README.md | 255 ++++++++++++++++++++++++--- packages/optimizely-sdk/README.md | 251 ++++++++++++++++++++------ packages/optimizely-sdk/package.json | 2 +- 3 files changed, 428 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index bc81907db..2d852c009 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,56 @@ -

- Optimizely JavaScript SDK -

+# Optimizely JavaScript SDK -

- This repository houses the official JavaScript SDK for use with Optimizely Full Stack and Optimizely Rollouts. -

+[![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) +[![npm](https://img.shields.io/npm/dm/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) +[![GitHub Actions](https://img.shields.io/github/actions/workflow/status/optimizely/javascript-sdk/javascript.yml)](https://github.com/optimizely/javascript-sdk/actions) +[![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) +[![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) -Optimizely Full Stack is A/B testing and feature flag management for product development teams. Experiment in any application. Make every feature on your roadmap an opportunity to learn. Learn more at https://www.optimizely.com/platform/full-stack/, or see the [documentation](https://docs.developers.optimizely.com/full-stack/docs). +This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). -Optimizely Rollouts is free feature flags for development teams. Easily roll out and roll back features in any application without code deploys. Mitigate risk for every feature on your roadmap. Learn more at https://www.optimizely.com/rollouts/, or see the [documentation](https://docs.developers.optimizely.com/rollouts/docs). +Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/welcome). +Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feature-flagging/) for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap. -## Packages +--- -This repository is a monorepo. It houses the main Javascript SDK and its supporting packages. +## Get Started -| Package | Version | Docs | Description | -| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | The Optimizely SDK | -| [`@optimizely/js-sdk-datafile-manager`](/packages/datafile-manager) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-datafile-manager.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-datafile-manager) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/initialize-sdk-javascript-node#customize-datafile-management-behavior) | Datafile Manager for Optimizely SDK -| [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | Event Batching support for Optimizely SDK -| [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | Logging Manager for Optimizely SDK -| [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | Utility functions for Optimizely packages +For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. -## About +For **React** applications, refer to the [React SDK developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-sdk). -`@optimizely/optimizely-sdk` is developed and maintained by [Optimizely](https://optimizely.com) and many [contributors](https://github.com/optimizely/javascript-sdk/graphs/contributors). If you're interested in learning more about what Optimizely X Full Stack can do for your company, please [get in touch](mailto:eng@optimizely.com)! +For **React Native** applications, refer to the [JavaScript (React Native) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-native-sdk). +For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk). -### Contributing +For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: +- [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) +- [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) +- [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) +- [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) +- [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) -Please see [CONTRIBUTING](CONTRIBUTING.md). +Note: These starter kits use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. -## Credits +### Prerequisites -First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manager/lib`, `packages/datafile-manager/src`, `packages/datafile-manager/__test__`, `packages/event-processor/src`, `packages/event-processor/__tests__`, `packages/logging/src`, `packages/logging/__tests__`, `packages/utils/src`, `packages/utils/__tests__`) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. +Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets any ES5-compliant JavaScript environment. We officially support: +- Node.js >= 8.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. +- [Modern Web Browsers, such as IE 10+, Firefox 21+, Safari 6+, and Chrome 23+](https://caniuse.com/#feat=es5) + +In addition, other environments are likely compatible but are not formally supported including: +- Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. +- [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. +- Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 + +### Requirements -## Additional Code +* JavaScript (Browser): Modern web browser that is ES5-compliant. -Prod dependencies are as follows: +* JavaScript (Node): Node 8.0+ + +* The following peer dependencies may be required for use in production: ```json { @@ -50,11 +62,11 @@ Prod dependencies are as follows: "publisher": "Kris Zyp", "repository": "https://github.com/kriszyp/json-schema" }, - "murmurhash@0.0.2": { + "murmurhash@2.0.1": { "licenses": "MIT*", "repository": "https://github.com/perezd/node-murmurhash" }, - "uuid@3.3.2": { + "uuid@8.3.2": { "licenses": "MIT", "repository": "https://github.com/kelektiv/node-uuid" }, @@ -64,3 +76,192 @@ Prod dependencies are as follows: } } ``` + +To regenerate this, run the following command: + +```sh +npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)' +``` + +and remove the self (`@optimizely/optimizely-sdk`) entry. + +> Note: The `jq` command line tool is required to run the above script. You may install `jq` to your environment by using Homebrew on MacOS using the command `brew install jq`. For Windows users, please visit the [JQ GitHub repository](https://github.com/stedolan/jq) to learn more. + +### Install the SDK + +Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk): + +Using `npm`: +```sh +npm install --save @optimizely/optimizely-sdk +``` + +Using `yarn`: +```sh +yarn add @optimizely/optimizely-sdk +``` + +Using `pnpm`: +```sh +pnpm add @optimizely/optimizely-sdk +``` + +Using `deno` (no installation required): +```javascript +import optimizely from "npm:@optimizely/optimizely-sdk" +``` + +### Packages + +This repository is a monorepo. It houses the main Javascript SDK and its supporting packages. + +| Package | Version | Docs | Description | +| - | - | - | - | +| [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | +| [`@optimizely/js-sdk-datafile-manager`](/packages/datafile-manager) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-datafile-manager.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-datafile-manager) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/initialize-sdk-javascript-node#customize-datafile-management-behavior) | (Consolidated*) Datafile Manager for Optimizely SDK +| [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | (Consolidated*) Event Batching support for Optimizely SDK +| [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK +| [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages + +> \* Consolidated packages have been copied over and included as modules within the main `@optimizely/optimizely-sdk` package to avoid requiring maintaining and utilizing multiple de-coupled dependencies. (Related PRs [#749](https://github.com/optimizely/javascript-sdk/pull/749), [#755](https://github.com/optimizely/javascript-sdk/pull/755/files), [#761](https://github.com/optimizely/javascript-sdk/pull/761), [#781](https://github.com/optimizely/javascript-sdk/pull/781)) + +## Use the JavaScript SDK (Browser) + +See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. + +### Initialization (Browser) + +The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): + +```html + + + + +``` + +When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. + +As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: + +```javascript +const optimizelyClient = window.optimizelySdk.createInstance({ + sdkKey: '', + // datafile: window.optimizelyDatafile, + // etc. +}) + +optimizelyClient.onReady().then(() => { + // Create the Optimizely user context, make decisions, and more here! +}) +``` + +Regarding `EventDispatcher`s: In Node.js and browser environments, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) modules and by [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Browser_compatibility), respectively. In all other environments, you must supply your own `EventDispatcher`. + +## Use the JavaScript SDK (Node) + +See the [Optimizely Feature Experimentation developer documentation for JavaScript (Node)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk) to learn how to set up your first JavaScript project and use the SDK for server-side applications. + +### Initialization (Node) + +The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): + +```typescript +import optimizelyClient from "@optimizely/optimizely-sdk"; + +optimizelyClient.createInstance({ + sdkKey: '', + // datafile: window.optimizelyDatafile, + // etc. +}); + +optimizelyClient.onReady().then(() => { + // Create the Optimizely user context, make decisions, and more here! +}) +``` + +Regarding `EventDispatcher`s: In Node.js environment, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) module. + +## SDK Development + +### Unit Tests + +There is a mix of testing paradigms used within the JavaScript SDK which include Mocha, Chai, Karma, and Jest, indicated by their respective `*.tests.js` and `*.spec.ts` filenames. + +When contributing code to the SDK, aim to keep the percentage of code test coverage at the current level ([![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk)) or above. + +To run unit tests on the primary JavaScript SDK package source code, you can take the following steps: + +1. On your command line or terminal, navigate to the `~/javascript-sdk/packages/optimizely-sdk` directory. +2. Ensure that you have run `npm install` to install all project dependencies. +3. Run `npm test` to run all test files. +4. (For cross-browser testing) Run `npm run test-xbrowser` to run tests in many browsers via BrowserStack. +5. Resolve any tests that fail before continuing with your contribution. + +This information is relevant only if you plan on contributing to the SDK itself. + +```sh +# Prerequisite: Install dependencies. +npm install + +# Run unit tests. +npm test + +# Run unit tests in many browsers, currently via BrowserStack. +# For this to work, the following environment variables must be set: +# - BROWSER_STACK_USERNAME +# - BROWSER_STACK_PASSWORD +npm run test-xbrowser +``` + +[/.github/workflows/javascript.yml](/.github/workflows/javascript.yml) contains the definitions for `BROWSER_STACK_USERNAME` and `BROWSER_STACK_ACCESS_KEY` used in the GitHub Actions CI pipeline. When developing locally, you must provide your own credentials in order to run `npm run test-xbrowser`. You can register for an account for free on [the BrowserStack official website here](https://www.browserstack.com/). + +### Contributing + +For more information regarding contributing to the Optimizely JavaScript SDK, please read [Contributing](CONTRIBUTING.md). + +## Special Notes + +### Migrating from 1.x.x + +This version represents a major version change and, as such, introduces some breaking changes: + +- The Node.js SDK is now combined with the JavaScript SDK. We now have just one package, `@optimizely/optimizely-sdk`, that works in many JavaScript environments. + +- We no longer support Node.js < 4.0.0, which collectively [reached end-of-life](https://github.com/nodejs/Release#end-of-life-releases) on 2016-12-31. + +- You will no longer be able to pass in `revenue` value as a stand-alone argument to the `track` call. Instead you will need to pass it as an entry in the [`eventTags`](https://developers.optimizely.com/x/solutions/sdks/reference/index.html?language=javascript#event-tags). + +### Feature Management access + +To access the Feature Management configuration in the Optimizely dashboard, please contact your Optimizely customer success manager. + +## Credits + +`@optimizely/optimizely-sdk` is developed and maintained by [Optimizely](https://optimizely.com) and many [contributors](https://github.com/optimizely/javascript-sdk/graphs/contributors). If you're interested in learning more about what Optimizely Feature Experimentation can do for your company you can visit the [official Optimizely Feature Experimentation product page here](https://www.optimizely.com/products/experiment/feature-experimentation/) to learn more. + +First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manager/lib`, `packages/datafile-manager/src`, `packages/datafile-manager/__test__`, `packages/event-processor/src`, `packages/event-processor/__tests__`, `packages/logging/src`, `packages/logging/__tests__`, `packages/utils/src`, `packages/utils/__tests__`) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. + +### Other Optimizely SDKs + +- Agent - https://github.com/optimizely/agent + +- Android - https://github.com/optimizely/android-sdk + +- C# - https://github.com/optimizely/csharp-sdk + +- Flutter - https://github.com/optimizely/optimizely-flutter-sdk + +- Go - https://github.com/optimizely/go-sdk + +- Java - https://github.com/optimizely/java-sdk + +- PHP - https://github.com/optimizely/php-sdk + +- Python - https://github.com/optimizely/python-sdk + +- React - https://github.com/optimizely/react-sdk + +- Ruby - https://github.com/optimizely/ruby-sdk + +- Swift - https://github.com/optimizely/swift-sdk \ No newline at end of file diff --git a/packages/optimizely-sdk/README.md b/packages/optimizely-sdk/README.md index d312b97a9..51b86419b 100644 --- a/packages/optimizely-sdk/README.md +++ b/packages/optimizely-sdk/README.md @@ -1,36 +1,135 @@ -# JavaScript SDK for Optimizely X Full Stack +# Optimizely JavaScript SDK + [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) [![npm](https://img.shields.io/npm/dm/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) -[![Travis CI](https://img.shields.io/travis/optimizely/javascript-sdk.svg)](https://travis-ci.org/optimizely/javascript-sdk) +[![GitHub Actions](https://img.shields.io/github/actions/workflow/status/optimizely/javascript-sdk/javascript.yml)](https://github.com/optimizely/javascript-sdk/actions) [![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) [![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) +This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). + +Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/welcome). + +Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feature-flagging/) for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap. + +--- -Optimizely X Full Stack is A/B testing and feature management for product development teams. Experiment in any application. Make every feature on your roadmap an opportunity to learn. Learn more at the [landing page](https://www.optimizely.com/products/full-stack/), or see the [documentation](https://docs.developers.optimizely.com/full-stack/docs). +## Get Started -This directory contains the source code for the JavaScript SDK, which is usable in Node.js, browsers, and beyond. +> For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. -## Getting Started +> For **React** applications, refer to the [React SDK developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-sdk). + +> For **React Native** applications, refer to the [JavaScript (React Native) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-native-sdk). + +> For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk). + +> For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: +> - [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) +> - [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) +> - [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) +> - [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) +> - [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) +> +> Note: These starter kits use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. ### Prerequisites Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets any ES5-compliant JavaScript environment. We officially support: - Node.js >= 8.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. -- [Web browsers](https://caniuse.com/#feat=es5) +- [Modern Web Browsers, such as IE 10+, Firefox 21+, Safari 6+, and Chrome 23+](https://caniuse.com/#feat=es5) -Other environments likely are compatible, too, but note that we don't officially support them: +In addition, other environments are likely compatible but are not formally supported including: - Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. - [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. - Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 -Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk). Using `npm`: +### Requirements + +* JavaScript (Browser): Modern web browser that is ES5-compliant. +* JavaScript (Node): Node 8.0+ + +* The following peer dependencies may be required for use in production: + +```json +{ + "json-schema@0.4.0": { + "licenses": [ + "AFLv2.1", + "BSD" + ], + "publisher": "Kris Zyp", + "repository": "https://github.com/kriszyp/json-schema" + }, + "murmurhash@2.0.1": { + "licenses": "MIT*", + "repository": "https://github.com/perezd/node-murmurhash" + }, + "uuid@8.3.2": { + "licenses": "MIT", + "repository": "https://github.com/kelektiv/node-uuid" + }, + "decompress-response@4.2.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/decompress-response" + } +} ``` + +To regenerate this, run the following command: + +```sh +npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)' +``` + +and remove the self (`@optimizely/optimizely-sdk`) entry. + +> Note: The `jq` command line tool is required to run the above script. You may install `jq` to your environment by using Homebrew on MacOS using the command `brew install jq`. For Windows users, please visit the [JQ GitHub repository](https://github.com/stedolan/jq) to learn more. + +### Install the SDK + +Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk): + +Using `npm`: +```sh npm install --save @optimizely/optimizely-sdk ``` -### Usage -See the Optimizely X Full Stack [developer documentation](http://developers.optimizely.com/server/reference/index.html) to learn how to set up your first JavaScript project and use the SDK. +Using `yarn`: +```sh +yarn add @optimizely/optimizely-sdk +``` + +Using `pnpm`: +```sh +pnpm add @optimizely/optimizely-sdk +``` + +Using `deno` (no installation required): +```javascript +import optimizely from "npm:@optimizely/optimizely-sdk" +``` + +### Packages + +This repository is a monorepo. It houses the main Javascript SDK and its supporting packages. + +| Package | Version | Docs | Description | +| - | - | - | - | +| [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | +| [`@optimizely/js-sdk-datafile-manager`](/packages/datafile-manager) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-datafile-manager.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-datafile-manager) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/initialize-sdk-javascript-node#customize-datafile-management-behavior) | (Consolidated*) Datafile Manager for Optimizely SDK +| [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | (Consolidated*) Event Batching support for Optimizely SDK +| [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK +| [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages + +> \* Consolidated packages have been copied over and included as modules within the main `@optimizely/optimizely-sdk` package to avoid requiring maintaining and utilizing multiple de-coupled dependencies. (Related PRs [#749](https://github.com/optimizely/javascript-sdk/pull/749), [#755](https://github.com/optimizely/javascript-sdk/pull/755/files), [#761](https://github.com/optimizely/javascript-sdk/pull/761), [#781](https://github.com/optimizely/javascript-sdk/pull/781)) + +## Use the JavaScript SDK (Browser) + +See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. + +### Initialization (Browser) The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): @@ -43,30 +142,69 @@ The package's entry point is a CommonJS module, which can be used directly in en When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. +As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: + +```javascript +const optimizelyClient = window.optimizelySdk.createInstance({ + sdkKey: '', + // datafile: window.optimizelyDatafile, + // etc. +}) + +optimizelyClient.onReady().then(() => { + // Create the Optimizely user context, make decisions, and more here! +}) +``` + Regarding `EventDispatcher`s: In Node.js and browser environments, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) modules and by [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Browser_compatibility), respectively. In all other environments, you must supply your own `EventDispatcher`. -### Migrating from 1.x.x +## Use the JavaScript SDK (Node) -This version represents a major version change and, as such, introduces some breaking changes: +See the [Optimizely Feature Experimentation developer documentation for JavaScript (Node)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk) to learn how to set up your first JavaScript project and use the SDK for server-side applications. -- The Node.js SDK is now combined with the JavaScript SDK. We now have just one package, `@optimizely/optimizely-sdk`, that works in many JavaScript environments. +### Initialization (Node) -- We no longer support Node.js < 4.0.0, which collectively [reached end-of-life](https://github.com/nodejs/Release#end-of-life-releases) on 2016-12-31. +The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): -- You will no longer be able to pass in `revenue` value as a stand-alone argument to the `track` call. Instead you will need to pass it as an entry in the [`eventTags`](https://developers.optimizely.com/x/solutions/sdks/reference/index.html?language=javascript#event-tags). +```typescript +import optimizelyClient from "@optimizely/optimizely-sdk"; -### Feature Management access +optimizelyClient.createInstance({ + sdkKey: '', + // datafile: window.optimizelyDatafile, + // etc. +}); + +optimizelyClient.onReady().then(() => { + // Create the Optimizely user context, make decisions, and more here! +}) +``` + +Regarding `EventDispatcher`s: In Node.js environment, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) module. -To access Feature Management in the Optimizely web application, please contact your Optimizely account executive. +## SDK Development + +### Unit Tests + +There is a mix of testing paradigms used within the JavaScript SDK which include Mocha, Chai, Karma, and Jest, indicated by their respective `*.tests.js` and `*.spec.ts` filenames. + +When contributing code to the SDK, aim to keep the percentage of code test coverage at the current level ([![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk)) or above. + +To run unit tests on the primary JavaScript SDK package source code, you can take the following steps: + +1. On your command line or terminal, navigate to the `~/javascript-sdk/packages/optimizely-sdk` directory. +2. Ensure that you have run `npm install` to install all project dependencies. +3. Run `npm test` to run all test files. +4. (For cross-browser testing) Run `npm run test-xbrowser` to run tests in many browsers via BrowserStack. +5. Resolve any tests that fail before continuing with your contribution. -## Contributing This information is relevant only if you plan on contributing to the SDK itself. ```sh # Prerequisite: Install dependencies. npm install -# Run unit tests with mocha. +# Run unit tests. npm test # Run unit tests in many browsers, currently via BrowserStack. @@ -76,45 +214,54 @@ npm test npm run test-xbrowser ``` -[.travis.yml](/.travis.yml) contains the definitions for `BROWSER_STACK_USERNAME` and `BROWSER_STACK_ACCESS_KEY` used in CI. These values are Optimizely's BrowserStack credentials, encrypted with our Travis CI public key. These creds can be rotated by following [these docs](https://docs.travis-ci.com/user/environment-variables/#Defining-encrypted-variables-in-.travis.yml). +[/.github/workflows/javascript.yml](/.github/workflows/javascript.yml) contains the definitions for `BROWSER_STACK_USERNAME` and `BROWSER_STACK_ACCESS_KEY` used in the GitHub Actions CI pipeline. When developing locally, you must provide your own credentials in order to run `npm run test-xbrowser`. You can register for an account for free on [the BrowserStack official website here](https://www.browserstack.com/). + +### Contributing + +For more information regarding contributing to the Optimizely JavaScript SDK, please read [Contributing](CONTRIBUTING.md). + +## Special Notes + +### Migrating from 1.x.x + +This version represents a major version change and, as such, introduces some breaking changes: + +- The Node.js SDK is now combined with the JavaScript SDK. We now have just one package, `@optimizely/optimizely-sdk`, that works in many JavaScript environments. + +- We no longer support Node.js < 4.0.0, which collectively [reached end-of-life](https://github.com/nodejs/Release#end-of-life-releases) on 2016-12-31. + +- You will no longer be able to pass in `revenue` value as a stand-alone argument to the `track` call. Instead you will need to pass it as an entry in the [`eventTags`](https://developers.optimizely.com/x/solutions/sdks/reference/index.html?language=javascript#event-tags). + +### Feature Management access + +To access the Feature Management configuration in the Optimizely dashboard, please contact your Optimizely customer success manager. ## Credits -First-party code (under lib/) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. +`@optimizely/optimizely-sdk` is developed and maintained by [Optimizely](https://optimizely.com) and many [contributors](https://github.com/optimizely/javascript-sdk/graphs/contributors). If you're interested in learning more about what Optimizely Feature Experimentation can do for your company you can visit the [official Optimizely Feature Experimentation product page here](https://www.optimizely.com/products/experiment/feature-experimentation/) to learn more. -## Additional Code +First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manager/lib`, `packages/datafile-manager/src`, `packages/datafile-manager/__test__`, `packages/event-processor/src`, `packages/event-processor/__tests__`, `packages/logging/src`, `packages/logging/__tests__`, `packages/utils/src`, `packages/utils/__tests__`) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. -Prod dependencies are as follows: +### Other Optimizely SDKs -```json -{ - "json-schema@0.2.3": { - "licenses": [ - "AFLv2.1", - "BSD" - ], - "publisher": "Kris Zyp", - "repository": "https://github.com/kriszyp/json-schema" - }, - "murmurhash@0.0.2": { - "licenses": "MIT*", - "repository": "https://github.com/perezd/node-murmurhash" - }, - "uuid@3.3.2": { - "licenses": "MIT", - "repository": "https://github.com/kelektiv/node-uuid" - }, - "decompress-response@4.2.1": { - "licenses": "MIT", - "repository": "https://github.com/sindresorhus/decompress-response" - } -} -``` +- Agent - https://github.com/optimizely/agent -To regenerate this, run the following command: +- Android - https://github.com/optimizely/android-sdk -```sh -npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)' -``` +- C# - https://github.com/optimizely/csharp-sdk -and remove the self (`@optimizely/optimizely-sdk`) entry. +- Flutter - https://github.com/optimizely/optimizely-flutter-sdk + +- Go - https://github.com/optimizely/go-sdk + +- Java - https://github.com/optimizely/java-sdk + +- PHP - https://github.com/optimizely/php-sdk + +- Python - https://github.com/optimizely/python-sdk + +- React - https://github.com/optimizely/react-sdk + +- Ruby - https://github.com/optimizely/ruby-sdk + +- Swift - https://github.com/optimizely/swift-sdk \ No newline at end of file diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index 46e836aec..a1d2607c4 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@optimizely/optimizely-sdk", "version": "4.9.2", - "description": "JavaScript SDK for Optimizely X Full Stack", + "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", "browser": "dist/optimizely.browser.min.js", From 88d711a9ac095a037b5872a3e5ee419495fda398 Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Fri, 17 Mar 2023 17:44:47 -0400 Subject: [PATCH 005/200] [FSSDK-8887] chore: Prepare release 4.9.3 (#804) --- packages/optimizely-sdk/CHANGELOG.md | 5 +++++ packages/optimizely-sdk/lib/index.browser.tests.js | 2 +- packages/optimizely-sdk/lib/index.lite.tests.js | 4 ++-- packages/optimizely-sdk/lib/index.node.tests.js | 4 ++-- packages/optimizely-sdk/lib/utils/enums/index.ts | 4 ++-- packages/optimizely-sdk/package-lock.json | 4 ++-- packages/optimizely-sdk/package.json | 2 +- packages/optimizely-sdk/tests/index.react_native.spec.ts | 4 ++-- packages/optimizely-sdk/tests/odpEventManager.spec.ts | 2 +- 9 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/optimizely-sdk/CHANGELOG.md b/packages/optimizely-sdk/CHANGELOG.md index ea8ba3e82..a99d86740 100644 --- a/packages/optimizely-sdk/CHANGELOG.md +++ b/packages/optimizely-sdk/CHANGELOG.md @@ -19,6 +19,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Safari versions earlier than `13.0`. - Dropped support for Node JS versions earlier than `14`. +## [4.9.3] - March 17, 2023 + +### Changed +- Updated README.md and package.json files to reflect that this SDK supports both Optimizely Feature Experimentation and Optimizely Full Stack ([#803](https://github.com/optimizely/javascript-sdk/pull/803)). + ## [4.9.2] - June 27, 2022 ### Changed diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index a4d4b71ba..659c01d62 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -186,7 +186,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.2'); + assert.equal(optlyInstance.clientVersion, '4.9.3'); }); it('should set the JavaScript client engine and version', function() { diff --git a/packages/optimizely-sdk/lib/index.lite.tests.js b/packages/optimizely-sdk/lib/index.lite.tests.js index 7e642722e..509093b40 100644 --- a/packages/optimizely-sdk/lib/index.lite.tests.js +++ b/packages/optimizely-sdk/lib/index.lite.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2021-2022 Optimizely + * Copyright 2021-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ describe('optimizelyFactory', function () { optlyInstance.onReady().catch(function () { }); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.2'); + assert.equal(optlyInstance.clientVersion, '4.9.3'); }); }); }); diff --git a/packages/optimizely-sdk/lib/index.node.tests.js b/packages/optimizely-sdk/lib/index.node.tests.js index 52ad076b8..45530b67b 100644 --- a/packages/optimizely-sdk/lib/index.node.tests.js +++ b/packages/optimizely-sdk/lib/index.node.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2020, 2022 Optimizely + * Copyright 2016-2020, 2022-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ describe('optimizelyFactory', function () { optlyInstance.onReady().catch(function () { }); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.2'); + assert.equal(optlyInstance.clientVersion, '4.9.3'); }); describe('event processor configuration', function () { diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index e31e48df9..4f2e62aea 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -220,8 +220,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '4.9.2'; -export const NODE_CLIENT_VERSION = '4.9.2'; +export const BROWSER_CLIENT_VERSION = '4.9.3'; +export const NODE_CLIENT_VERSION = '4.9.3'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json index 276e04757..6383bb03b 100644 --- a/packages/optimizely-sdk/package-lock.json +++ b/packages/optimizely-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "4.9.2", + "version": "4.9.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "4.9.2", + "version": "4.9.3", "license": "Apache-2.0", "dependencies": { "@optimizely/js-sdk-datafile-manager": "^0.9.5", diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index a1d2607c4..792677749 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "4.9.2", + "version": "4.9.3", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/packages/optimizely-sdk/tests/index.react_native.spec.ts b/packages/optimizely-sdk/tests/index.react_native.spec.ts index 1717bdf55..311ca9080 100644 --- a/packages/optimizely-sdk/tests/index.react_native.spec.ts +++ b/packages/optimizely-sdk/tests/index.react_native.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, 2022 Optimizely + * Copyright 2019-2020, 2022-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('4.9.2'); + expect(optlyInstance.clientVersion).toEqual('4.9.3'); }); it('should set the React Native JS client engine and javascript SDK version', () => { diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index 8a07463ae..944dc156e 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -55,7 +55,7 @@ const EVENTS: OdpEvent[] = [ ]; // naming for object destructuring const clientEngine = 'javascript-sdk'; -const clientVersion = '4.9.2'; +const clientVersion = '4.9.3'; const PROCESSED_EVENTS: OdpEvent[] = [ new OdpEvent( 't1', From 8cce2f32b3732d59315a35cf214fca4783f463d0 Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Thu, 23 Mar 2023 12:44:43 -0400 Subject: [PATCH 006/200] Update OdpOptions to handle additional granular configuration options for ODP Segment and Event Managers (#805) --- .../lib/core/odp/odp_event_manager.ts | 1 + .../lib/core/odp/odp_manager.ts | 49 +-- .../optimizely-sdk/lib/index.browser.tests.js | 40 +- packages/optimizely-sdk/lib/index.browser.ts | 2 +- .../lib/optimizely/index.tests.js | 15 +- .../optimizely-sdk/lib/optimizely/index.ts | 2 +- .../lib/plugins/odp_manager/index.browser.ts | 77 ++-- .../lib/plugins/odp_manager/index.node.ts | 26 +- packages/optimizely-sdk/lib/shared_types.ts | 8 + .../lib/utils/lru_cache/server_lru_cache.ts | 7 +- .../tests/odpManager.browser.spec.ts | 397 +++++++++++++++++- .../optimizely-sdk/tests/odpManager.spec.ts | 78 ++-- 12 files changed, 552 insertions(+), 150 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index d372dac66..41478f08b 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -143,6 +143,7 @@ export class OdpEventManager implements IOdpEventManager { let defaultQueueSize = DEFAULT_BROWSER_QUEUE_SIZE; try { + // TODO: Consider refactoring to use typeof process and combine w/above line if (process) { defaultQueueSize = DEFAULT_SERVER_QUEUE_SIZE; } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts index 00d413aa6..002405968 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts @@ -19,7 +19,7 @@ import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; import { RequestHandler } from './../../utils/http_request_handler/http'; -import { BrowserLRUCache } from './../../utils/lru_cache/browser_lru_cache'; + import { LRUCache } from './../../utils/lru_cache/lru_cache'; import { VuidManager } from '../../plugins/vuid_manager'; @@ -32,26 +32,25 @@ import { OdpEventApiManager } from './odp_event_api_manager'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { invalidOdpDataFound } from './odp_utils'; import { OdpEvent } from './odp_event'; +import { OdpOptions } from '../../shared_types'; /** - * @param {boolean} disable Flag for disabling ODP Manager. - * @param {RequestHandler} requestHandler HTTP request handler that will be used by Segment and Event Managers. + * @param {LRUCache[]} segmentLRUCache Cache to be used for storing segments. + * @param {RequestHandler} segmentRequestHandler HTTP request handler that will be used by the ODP Segment Manager. + * @param {RequestHandler} eventRequestHandler HTTP request handler that will be used by the ODP Event Manager. * @param {LogHandler} logger (Optional) Accepts custom LogHandler. Defaults to the default global LogHandler. * @param {string} clientEngine (Optional) String denoting specific client engine being used. Defaults to 'javascript-sdk'. * @param {string} clientVersion (Optional) String denoting specific client version. Defaults to current version value from package.json. - * @param {LRUCache} segmentsCache (Optional) Accepts a custom LRUCache. Defaults to BrowserLRUCache. - * @param {OdpEventManager} eventManager (Optional) Accepts a custom ODPEventManager. - * @param {OdpSegmentManager} segmentManager (Optional) Accepts a custom ODPSegmentManager. + * @param {OdpOptions} odpOptions (Optional) Configuration settings for various ODP options from segment cache size to event flush interval. */ interface OdpManagerConfig { - disable: boolean; - requestHandler: RequestHandler; + segmentLRUCache: LRUCache; + segmentRequestHandler: RequestHandler; + eventRequestHandler: RequestHandler; logger?: LogHandler; clientEngine?: string; clientVersion?: string; - segmentsCache?: LRUCache; - eventManager?: OdpEventManager; - segmentManager?: OdpSegmentManager; + odpOptions?: OdpOptions; } /** @@ -75,16 +74,15 @@ export class OdpManager { public eventManager: OdpEventManager | undefined; constructor({ - disable, - requestHandler, + segmentLRUCache, + segmentRequestHandler, + eventRequestHandler, logger, clientEngine, clientVersion, - segmentsCache, - eventManager, - segmentManager, + odpOptions, }: OdpManagerConfig) { - this.enabled = !disable; + this.enabled = !odpOptions?.disabled; this.logger = logger || getLogger(); if (!this.enabled) { @@ -93,28 +91,31 @@ export class OdpManager { } // Set up Segment Manager (Audience Segments GraphQL API Interface) - if (segmentManager) { - this.segmentManager = segmentManager; + if (odpOptions?.segmentManager) { + this.segmentManager = odpOptions.segmentManager; this.segmentManager.updateSettings(this.odpConfig); } else { this.segmentManager = new OdpSegmentManager( this.odpConfig, - segmentsCache || new BrowserLRUCache(), - new OdpSegmentApiManager(requestHandler, this.logger) + segmentLRUCache, + new OdpSegmentApiManager(segmentRequestHandler, this.logger) ); } // Set up Events Manager (Events REST API Interface) - if (eventManager) { - this.eventManager = eventManager; + if (odpOptions?.eventManager) { + this.eventManager = odpOptions.eventManager; this.eventManager.updateSettings(this.odpConfig); } else { this.eventManager = new OdpEventManager({ odpConfig: this.odpConfig, - apiManager: new OdpEventApiManager(requestHandler, this.logger), + apiManager: new OdpEventApiManager(eventRequestHandler, this.logger), logger: this.logger, clientEngine: clientEngine || 'javascript-sdk', clientVersion: clientVersion || BROWSER_CLIENT_VERSION, + flushInterval: odpOptions?.eventFlushInterval, + batchSize: odpOptions?.eventBatchSize, + queueSize: odpOptions?.eventQueueSize, }); } diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index 659c01d62..ec1400959 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -25,11 +25,9 @@ import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import OptimizelyUserContext from './optimizely_user_context'; -import { LOG_MESSAGES, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from './utils/enums'; -import { BrowserLRUCache } from './utils/lru_cache'; -import { OdpConfig } from './core/odp/odp_config'; +import { LOG_MESSAGES, ODP_EVENT_ACTION } from './utils/enums'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; -import { OdpEvent } from './core/odp/odp_event'; +import { OdpConfig } from './core/odp/odp_config'; var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; @@ -612,7 +610,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: BrowserOdpManager.createBrowserOdpManager({ + odpManager: new BrowserOdpManager({ logger, odpOptions: { disabled: true, @@ -630,7 +628,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: BrowserOdpManager.createBrowserOdpManager({ + odpManager: new BrowserOdpManager({ logger, odpOptions: { segmentsCacheSize: 10, @@ -652,7 +650,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: BrowserOdpManager.createBrowserOdpManager({ + odpManager: new BrowserOdpManager({ logger, odpOptions: { segmentsCacheTimeout: 10, @@ -674,7 +672,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: BrowserOdpManager.createBrowserOdpManager({ + odpManager: new BrowserOdpManager({ logger, odpOptions: { segmentsCacheSize: 10, @@ -696,7 +694,8 @@ describe('javascript-sdk (Browser)', function() { ); }); - it('should accept a valid custom odp segment manager', () => { + // TODO: Patch VUID Promise Pattern (@Andy) + it('should accept a valid custom odp segment manager', async () => { const fakeSegmentManager = { fetchQualifiedSegments: sinon.spy(), updateSettings: sinon.spy(), @@ -708,7 +707,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: BrowserOdpManager.createBrowserOdpManager({ + odpManager: new BrowserOdpManager({ logger, odpOptions: { segmentManager: fakeSegmentManager, @@ -716,9 +715,18 @@ describe('javascript-sdk (Browser)', function() { }), }); - client.fetchQualifiedSegments(testVuid); + sinon.assert.called(fakeSegmentManager.updateSettings); + + try { + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isEmpty(readyData.reason); + + await client.fetchQualifiedSegments(testVuid); - sinon.assert.calledWith(fakeSegmentManager.updateSettings, new OdpConfig()); + sinon.assert.notCalled(logger.error); + sinon.assert.called(fakeSegmentManager.fetchQualifiedSegments); + } catch (e) {} }); it('should accept a valid custom odp event manager', () => { @@ -738,7 +746,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: BrowserOdpManager.createBrowserOdpManager({ + odpManager: new BrowserOdpManager({ logger, odpOptions: { eventManager: fakeEventManager, @@ -749,7 +757,7 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.called(fakeEventManager.start); }); - // TODO: Finish this test + // TODO: Patch VUID Promise Pattern (@Andy) it('should send an odp event with sendOdpEvent', async () => { const fakeOdpManager = { sendEvent: sinon.spy(), @@ -780,7 +788,7 @@ describe('javascript-sdk (Browser)', function() { } catch (e) {} }); - // TODO: Finish this test + // TODO: Patch VUID Promise Pattern (@Andy) it('should log an error when attempting to send an odp event when odp is disabled', async () => { const client = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), @@ -788,7 +796,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: BrowserOdpManager.createBrowserOdpManager({ + odpManager: new BrowserOdpManager({ logger, odpOptions: { disabled: true, diff --git a/packages/optimizely-sdk/lib/index.browser.ts b/packages/optimizely-sdk/lib/index.browser.ts index 7f6525c74..153e86ed1 100644 --- a/packages/optimizely-sdk/lib/index.browser.ts +++ b/packages/optimizely-sdk/lib/index.browser.ts @@ -125,7 +125,7 @@ const createInstance = function(config: Config): Client | null { : undefined, notificationCenter, isValidInstance: isValidInstance, - odpManager: BrowserOdpManager.createBrowserOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), }; const optimizely = new BrowserOptimizely(optimizelyOptions); diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index d4893a60a..30fecc0f1 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -17,7 +17,6 @@ import { assert, expect } from 'chai'; import sinon from 'sinon'; import { sprintf } from '../utils/fns'; import { NOTIFICATION_TYPES } from '../utils/enums'; -import eventProcessor from '../plugins/event_processor'; import * as logging from '../modules/logging'; import Optimizely from './'; @@ -40,8 +39,7 @@ import { createForwardingEventProcessor } from '../plugins/event_processor/forwa import { createEventProcessor } from '../plugins/event_processor'; import { createNotificationCenter } from '../core/notification_center'; import { createHttpPollingDatafileManager } from '../plugins/datafile_manager/http_polling_datafile_manager'; -import { OdpManager } from '../core/odp/odp_manager'; -import { getLogger } from '../modules/logging'; +import { NodeOdpManager } from '../plugins/odp_manager/index.node'; var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; @@ -10088,6 +10086,7 @@ describe('lib/optimizely', function() { }); }); + // Note: /lib/index.browser.tests.js contains relevant Opti Client x Browser ODP Tests // TODO: Finish these tests in ODP Node.js Implementation describe('odp', () => { var optlyInstanceWithOdp; @@ -10112,9 +10111,7 @@ describe('lib/optimizely', function() { eventBatchSize: 1, eventProcessor, notificationCenter, - odpManager: new OdpManager({ - disable: false, - }), + odpManager: new NodeOdpManager({}), }); bucketStub = sinon.stub(bucketer, 'bucket'); @@ -10142,9 +10139,11 @@ describe('lib/optimizely', function() { eventBatchSize: 1, eventProcessor, notificationCenter, - odpManager: new OdpManager({ - disable: true, + odpManager: new NodeOdpManager({ logger: createdLogger, + odpOptions: { + disabled: true, + }, }), }); diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index f1cd77ab3..69714e096 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1663,7 +1663,7 @@ export default class Optimizely { * Sends an action as an ODP Event with optional custom parameters including type, identifiers, and data * Note: Since this depends on this.odpManager, it must await Optimizely client's onReady() promise resolution. * @param {Object} odpEvent - * @param {ODP_EVENT_ACTION} odpEvent.action Subcategory of the event type (i.e. "client_initialized", or "") + * @param {ODP_EVENT_ACTION} odpEvent.action Subcategory of the event type (i.e. "client_initialized", or "identified") * @param {string} odpEvent.type (Optional) Type of event (Defaults to "fullstack") * @param {Map} odpEvent.identifiers (Optional) Key-value map of user identifiers * @param {Map} odpEvent.data (Optional) Event data in a key-value map. diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts index d15b2dc2f..76c1b34f4 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts @@ -16,26 +16,22 @@ import { BROWSER_CLIENT_VERSION, ERROR_MESSAGES, JAVASCRIPT_CLIENT_ENGINE, ODP_USER_KEY } from '../../utils/enums'; import { getLogger, LoggerFacade, LogHandler, LogLevel } from '../../modules/logging'; + import { BrowserRequestHandler } from './../../utils/http_request_handler/browser_request_handler'; import BrowserAsyncStorageCache from '../key_value_cache/browserAsyncStorageCache'; import PersistentKeyValueCache from '../key_value_cache/persistentKeyValueCache'; -import { BrowserLRUCache, LRUCache } from '../../utils/lru_cache'; +import { BrowserLRUCache } from '../../utils/lru_cache'; import { VuidManager } from './../vuid_manager/index'; import { OdpManager } from '../../core/odp/odp_manager'; import { OdpEvent } from '../../core/odp/odp_event'; -import { OdpEventManager } from '../../core/odp/odp_event_manager'; -import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; import { OdpOptions } from '../../shared_types'; interface BrowserOdpManagerConfig { - disable: boolean; logger?: LogHandler; - segmentsCache?: LRUCache; - eventManager?: OdpEventManager; - segmentManager?: OdpSegmentManager; + odpOptions?: OdpOptions; } // Client-side Browser Plugin for ODP Manager @@ -43,22 +39,41 @@ export class BrowserOdpManager extends OdpManager { static cache = new BrowserAsyncStorageCache(); vuid?: string; - constructor({ disable, logger, segmentsCache, eventManager, segmentManager }: BrowserOdpManagerConfig) { - const browserLogger = logger || getLogger(); + constructor({ logger, odpOptions }: BrowserOdpManagerConfig) { + const browserLogger = logger || getLogger('BrowserOdpManager'); - const browserRequestHandler = new BrowserRequestHandler(browserLogger); const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; const browserClientVersion = BROWSER_CLIENT_VERSION; + let customSegmentRequestHandler; + + if (odpOptions?.segmentsRequestHandler) { + customSegmentRequestHandler = odpOptions.segmentsRequestHandler; + } else if (odpOptions?.segmentsApiTimeout) { + customSegmentRequestHandler = new BrowserRequestHandler(browserLogger, odpOptions.segmentsApiTimeout); + } + + let customEventRequestHandler; + + if (odpOptions?.eventRequestHandler) { + customEventRequestHandler = odpOptions.eventRequestHandler; + } else if (odpOptions?.eventApiTimeout) { + customEventRequestHandler = new BrowserRequestHandler(browserLogger, odpOptions.eventApiTimeout); + } + super({ - disable, - requestHandler: browserRequestHandler, + segmentLRUCache: + odpOptions?.segmentsCache || + new BrowserLRUCache({ + maxSize: odpOptions?.segmentsCacheSize, + timeout: odpOptions?.segmentsCacheTimeout, + }), + segmentRequestHandler: customSegmentRequestHandler || new BrowserRequestHandler(browserLogger), + eventRequestHandler: customEventRequestHandler || new BrowserRequestHandler(browserLogger), logger: browserLogger, clientEngine: browserClientEngine, clientVersion: browserClientVersion, - segmentsCache: segmentsCache || new BrowserLRUCache(), - eventManager, - segmentManager, + odpOptions, }); this.logger = browserLogger; @@ -99,7 +114,12 @@ export class BrowserOdpManager extends OdpManager { return; } - super.identifyUser(fsUserId, vuid); + if (fsUserId && vuid && VuidManager.isVuid(vuid)) { + super.identifyUser(fsUserId, vuid); + return; + } + + super.identifyUser(fsUserId, vuid || this.vuid); } /** @@ -122,29 +142,4 @@ export class BrowserOdpManager extends OdpManager { super.sendEvent({ type, action, identifiers: identifiersWithVuid, data }); } - - public static createBrowserOdpManager({ - logger = getLogger(), - odpOptions, - }: { - logger: LoggerFacade; - odpOptions?: OdpOptions; - }): BrowserOdpManager { - if (!odpOptions) { - return new BrowserOdpManager({ disable: false, logger }); - } - - return new BrowserOdpManager({ - disable: odpOptions.disabled || false, - segmentsCache: - odpOptions?.segmentsCache || - new BrowserLRUCache({ - maxSize: odpOptions.segmentsCacheSize, - timeout: odpOptions.segmentsCacheTimeout, - }), - segmentManager: odpOptions.segmentManager, - eventManager: odpOptions.eventManager, - logger, - }); - } } diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts index 186f6925d..9b6e1a657 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts @@ -16,21 +16,17 @@ import { NodeRequestHandler } from '../../utils/http_request_handler/node_request_handler'; -import { LRUCache } from '../../utils/lru_cache'; import { ServerLRUCache } from './../../utils/lru_cache/server_lru_cache'; import { OdpManager } from '../../core/odp/odp_manager'; -import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; -import { OdpEventManager } from '../../core/odp/odp_event_manager'; import { getLogger, LogHandler } from '../../modules/logging'; -import { ERROR_MESSAGES, LOG_LEVEL, NODE_CLIENT_ENGINE, NODE_CLIENT_VERSION } from '../../utils/enums'; +import { NODE_CLIENT_ENGINE, NODE_CLIENT_VERSION } from '../../utils/enums'; +import { OdpOptions } from '../../../lib/shared_types'; interface NodeOdpManagerConfig { disable: boolean; logger?: LogHandler; - segmentsCache?: LRUCache; - segmentManager?: OdpSegmentManager; - eventManager?: OdpEventManager; + odpOptions?: OdpOptions; } /** @@ -38,7 +34,7 @@ interface NodeOdpManagerConfig { * Note: As this is still a work-in-progress. Please avoid using the Node ODP Manager. */ export class NodeOdpManager extends OdpManager { - constructor({ disable, logger, segmentsCache, segmentManager, eventManager }: NodeOdpManagerConfig) { + constructor({ logger, odpOptions }: NodeOdpManagerConfig) { const nodeLogger = logger || getLogger(); const nodeRequestHandler = new NodeRequestHandler(nodeLogger); @@ -46,14 +42,18 @@ export class NodeOdpManager extends OdpManager { const nodeClientVersion = NODE_CLIENT_VERSION; super({ - disable, - requestHandler: nodeRequestHandler, + segmentLRUCache: + odpOptions?.segmentsCache || + new ServerLRUCache({ + maxSize: odpOptions?.segmentsCacheSize, + timeout: odpOptions?.segmentsCacheTimeout, + }), + segmentRequestHandler: nodeRequestHandler, + eventRequestHandler: nodeRequestHandler, logger: nodeLogger, clientEngine: nodeClientEngine, clientVersion: nodeClientVersion, - segmentsCache: segmentsCache || new ServerLRUCache(), - segmentManager, - eventManager, + odpOptions, }); } } diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index 377890c9c..4680bb12d 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -23,6 +23,7 @@ import { OdpManager } from './core/odp/odp_manager'; import { OdpSegmentManager } from './core/odp/odp_segment_manager'; import { LRUCache } from './utils/lru_cache'; import { OdpEventManager } from './core/odp/odp_event_manager'; +import { RequestHandler } from '../lib/utils/http_request_handler/http'; export interface BucketerParams { experimentId: string; @@ -84,7 +85,14 @@ export interface OdpOptions { segmentsCache?: LRUCache; segmentsCacheSize?: number; segmentsCacheTimeout?: number; + segmentsApiTimeout?: number; + segmentsRequestHandler?: RequestHandler; segmentManager?: OdpSegmentManager; + eventFlushInterval?: number; + eventBatchSize?: number; + eventQueueSize?: number; + eventApiTimeout?: number; + eventRequestHandler?: RequestHandler; eventManager?: OdpEventManager; } diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts index ebcd5f9ea..578daa479 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts @@ -16,13 +16,18 @@ import LRUCache, { ISegmentsCacheConfig, LRUCacheConfig } from './lru_cache'; +export interface ServerLRUCacheConfig { + maxSize?: number; + timeout?: number; +} + export const ServerLRUCacheConfig: ISegmentsCacheConfig = { DEFAULT_CAPACITY: 10000, DEFAULT_TIMEOUT_SECS: 600, }; export class ServerLRUCache extends LRUCache { - constructor(config?: LRUCacheConfig) { + constructor(config?: ServerLRUCacheConfig) { super({ maxSize: config?.maxSize || ServerLRUCacheConfig.DEFAULT_CAPACITY, timeout: config?.timeout || ServerLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts index bc63c9412..313a0d069 100644 --- a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -24,6 +24,7 @@ import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; import { BrowserOdpManager } from './../lib/plugins/odp_manager/index.browser'; +import { OdpOptions } from './../lib/shared_types'; import { OdpConfig } from '../lib/core/odp/odp_config'; import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; @@ -31,6 +32,7 @@ import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; import { OdpEvent } from '../lib/core/odp/odp_event'; +import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -101,9 +103,10 @@ describe('OdpManager', () => { const browserOdpManagerInstance = () => new BrowserOdpManager({ - disable: false, - eventManager: fakeEventManager, - segmentManager: fakeSegmentManager, + odpOptions: { + eventManager: fakeEventManager, + segmentManager: fakeSegmentManager, + }, }); it('should register VUID automatically on BrowserOdpManager initialization', async () => { @@ -113,7 +116,7 @@ describe('OdpManager', () => { }); it('should drop relevant calls when OdpManager is initialized with the disabled flag, except for VUID', async () => { - const browserOdpManager = new BrowserOdpManager({ disable: true, logger: fakeLogger }); + const browserOdpManager = new BrowserOdpManager({ logger: fakeLogger, odpOptions: { disabled: true } }); verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); @@ -149,8 +152,9 @@ describe('OdpManager', () => { it('should use new settings in event manager when ODP Config is updated', async () => { const browserOdpManager = new BrowserOdpManager({ - disable: false, - eventManager: fakeEventManager, + odpOptions: { + eventManager: fakeEventManager, + }, }); expect(browserOdpManager.eventManager).toBeDefined(); @@ -186,14 +190,24 @@ describe('OdpManager', () => { it('should use new settings in segment manager when ODP Config is updated', () => { const browserOdpManager = new BrowserOdpManager({ - disable: false, - segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), fakeSegmentApiManager), + odpOptions: { + segmentManager: new OdpSegmentManager( + odpConfig, + new BrowserLRUCache(), + fakeSegmentApiManager + ), + }, }); const didUpdateA = browserOdpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); expect(didUpdateA).toBe(true); browserOdpManager.fetchQualifiedSegments(vuidA); + + verify( + mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING) + ).never(); + const fetchQualifiedSegmentsArgsA = capture(mockSegmentApiManager.fetchSegments).last(); expect(fetchQualifiedSegmentsArgsA).toStrictEqual([keyA, hostA, ODP_USER_KEY.VUID, vuidA, segmentsA]); @@ -210,9 +224,7 @@ describe('OdpManager', () => { const browserOdpManagerA = browserOdpManagerInstance(); expect(browserOdpManagerA.eventManager).not.toBe(null); - const browserOdpManagerB = new BrowserOdpManager({ - disable: false, - }); + const browserOdpManagerB = new BrowserOdpManager({}); expect(browserOdpManagerB.eventManager).not.toBe(null); }); @@ -220,16 +232,15 @@ describe('OdpManager', () => { const browserOdpManagerA = browserOdpManagerInstance(); expect(browserOdpManagerA.segmentManager).not.toBe(null); - const browserOdpManagerB = new BrowserOdpManager({ - disable: false, - }); + const browserOdpManagerB = new BrowserOdpManager({}); expect(browserOdpManagerB.eventManager).not.toBe(null); }); it("should call event manager's sendEvent if ODP Event is valid", () => { const browserOdpManager = new BrowserOdpManager({ - disable: false, - eventManager: fakeEventManager, + odpOptions: { + eventManager: fakeEventManager, + }, }); const odpConfig = new OdpConfig('key', 'host', []); @@ -253,4 +264,358 @@ describe('OdpManager', () => { browserOdpManager.sendEvent(invalidOdpEvent); }).toThrow(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING); }); + + describe('Populates BrowserOdpManager correctly with all odpOptions', () => { + it('odpOptions.disabled = true disables BrowserOdpManager', () => { + const odpOptions: OdpOptions = { + disabled: true, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + expect(browserOdpManager.enabled).toBe(false); + }); + + it('Custom odpOptions.segmentsCache overrides default LRUCache', () => { + const odpOptions: OdpOptions = { + segmentsCache: new BrowserLRUCache({ + maxSize: 2, + timeout: 4000, + }), + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(2); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4000); + }); + + it('Custom odpOptions.segmentsCacheSize overrides default LRUCache size', () => { + const odpOptions: OdpOptions = { + segmentsCacheSize: 2, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(2); + }); + + it('Custom odpOptions.segmentsCacheTimeout overrides default LRUCache timeout', () => { + const odpOptions: OdpOptions = { + segmentsCacheTimeout: 4000, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4000); + }); + + it('Custom odpOptions.segmentsApiTimeout overrides default Segment API Request Handler timeout', () => { + const odpOptions: OdpOptions = { + segmentsApiTimeout: 4000, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4000); + }); + + it('Custom odpOptions.segmentsRequestHandler overrides default Segment API Request Handler', () => { + const odpOptions: OdpOptions = { + segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4000); + }); + + it('Custom odpOptions.segmentRequestHandler override takes precedence over odpOptions.eventApiTimeout', () => { + const odpOptions: OdpOptions = { + segmentsApiTimeout: 2, + segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 1), + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(1); + }); + + it('Custom odpOptions.segmentManager overrides default Segment Manager', () => { + const customSegmentManager = new OdpSegmentManager( + odpConfig, + new BrowserLRUCache(), + fakeSegmentApiManager + ); + + const odpOptions: OdpOptions = { + segmentManager: customSegmentManager, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager).toBe(customSegmentManager); + }); + + it('Custom odpOptions.segmentManager override takes precedence over all other segments-related odpOptions', () => { + const customSegmentManager = new OdpSegmentManager( + odpConfig, + new BrowserLRUCache({ + maxSize: 1, + timeout: 1, + }), + new OdpSegmentApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger) + ); + + const odpOptions: OdpOptions = { + segmentsCacheSize: 2, + segmentsCacheTimeout: 2, + segmentsCache: new BrowserLRUCache({ maxSize: 2, timeout: 2 }), + segmentsApiTimeout: 2, + segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 2), + segmentManager: customSegmentManager, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(1); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(1); + + // @ts-ignore + expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(1); + + // @ts-ignore + expect(browserOdpManager.segmentManager).toBe(customSegmentManager); + }); + + it('Custom odpOptions.eventApiTimeout overrides default Event API Request Handler timeout', () => { + const odpOptions: OdpOptions = { + eventApiTimeout: 4000, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(4000); + }); + + it('Custom odpOptions.eventFlushInterval overrides default Event Manager flush interval', () => { + const odpOptions: OdpOptions = { + eventFlushInterval: 4000, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.flushInterval).toBe(4000); + }); + + it('Custom odpOptions.eventBatchSize overrides default Event Manager batch size', () => { + const odpOptions: OdpOptions = { + eventBatchSize: 2, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.batchSize).toBe(2); + }); + + it('Custom odpOptions.eventQueueSize overrides default Event Manager queue size', () => { + const odpOptions: OdpOptions = { + eventQueueSize: 2, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.queueSize).toBe(2); + }); + + it('Custom odpOptions.eventRequestHandler overrides default Event Manager request handler', () => { + const odpOptions: OdpOptions = { + eventRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(4000); + }); + + it('Custom odpOptions.eventRequestHandler override takes precedence over odpOptions.eventApiTimeout', () => { + const odpOptions: OdpOptions = { + eventApiTimeout: 2, + eventBatchSize: 2, + eventFlushInterval: 2, + eventQueueSize: 2, + eventRequestHandler: new BrowserRequestHandler(fakeLogger, 1), + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(1); + }); + + it('Custom odpOptions.eventManager overrides default Event Manager', () => { + const fakeClientEngine = 'test-javascript-sdk'; + const fakeClientVersion = '1.2.3'; + + const customEventManager = new OdpEventManager({ + odpConfig, + apiManager: fakeEventApiManager, + logger: fakeLogger, + clientEngine: fakeClientEngine, + clientVersion: fakeClientVersion, + }); + + const odpOptions: OdpOptions = { + eventManager: customEventManager, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager).toBe(customEventManager); + + // @ts-ignore + expect(browserOdpManager.eventManager.clientEngine).toBe(fakeClientEngine); + + // @ts-ignore + expect(browserOdpManager.eventManager.clientVersion).toBe(fakeClientVersion); + }); + + it('Custom odpOptions.eventManager override takes precedence over all other event-related odpOptions', () => { + const fakeClientEngine = 'test-javascript-sdk'; + const fakeClientVersion = '1.2.3'; + + const customEventManager = new OdpEventManager({ + odpConfig, + apiManager: new OdpEventApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger), + logger: fakeLogger, + clientEngine: fakeClientEngine, + clientVersion: fakeClientVersion, + queueSize: 1, + batchSize: 1, + flushInterval: 1, + }); + + const odpOptions: OdpOptions = { + eventApiTimeout: 2, + eventBatchSize: 2, + eventFlushInterval: 2, + eventQueueSize: 2, + eventRequestHandler: new BrowserRequestHandler(fakeLogger, 3), + eventManager: customEventManager, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager).toBe(customEventManager); + + // @ts-ignore + expect(browserOdpManager.eventManager.clientEngine).toBe(fakeClientEngine); + + // @ts-ignore + expect(browserOdpManager.eventManager.clientVersion).toBe(fakeClientVersion); + + // @ts-ignore + expect(browserOdpManager.eventManager.batchSize).toBe(1); + + // @ts-ignore + expect(browserOdpManager.eventManager.flushInterval).toBe(1); + + // @ts-ignore + expect(browserOdpManager.eventManager.queueSize).toBe(1); + + // @ts-ignore + expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(1); + }); + + it('Custom odpOptions micro values (non-request/manager) override all expected fields for both segments and event managers', () => { + const odpOptions: OdpOptions = { + segmentsCacheSize: 4, + segmentsCacheTimeout: 4, + segmentsCache: new BrowserLRUCache({ maxSize: 4, timeout: 4 }), + segmentsApiTimeout: 4, + eventApiTimeout: 4, + eventBatchSize: 4, + eventFlushInterval: 4, + eventQueueSize: 4, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(4); + + // @ts-ignore + expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4); + + // @ts-ignore + expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4); + + // @ts-ignore + expect(browserOdpManager.eventManager.batchSize).toBe(4); + + // @ts-ignore + expect(browserOdpManager.eventManager.flushInterval).toBe(4); + + // @ts-ignore + expect(browserOdpManager.eventManager.queueSize).toBe(4); + + // @ts-ignore + expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(4); + }); + }); }); diff --git a/packages/optimizely-sdk/tests/odpManager.spec.ts b/packages/optimizely-sdk/tests/odpManager.spec.ts index 58c0da371..9fd12605d 100644 --- a/packages/optimizely-sdk/tests/odpManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.spec.ts @@ -1,3 +1,4 @@ +import { LRUCache } from './../lib/utils/lru_cache/lru_cache'; /** * Copyright 2023, Optimizely * @@ -30,6 +31,7 @@ import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; import { OdpEventManager } from '../lib/core/odp/odp_event_manager'; import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; +import { ServerLRUCache } from '../lib/utils/lru_cache'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -47,7 +49,7 @@ describe('OdpManager', () => { let odpConfig: OdpConfig; let logger: LogHandler; - let requestHandler: RequestHandler; + let defaultRequestHandler: RequestHandler; let mockEventApiManager: OdpEventApiManager; let mockEventManager: OdpEventManager; @@ -65,7 +67,7 @@ describe('OdpManager', () => { odpConfig = new OdpConfig(); logger = instance(mockLogger); - requestHandler = instance(mockRequestHandler); + defaultRequestHandler = instance(mockRequestHandler); mockEventApiManager = mock(); mockEventManager = mock(); @@ -88,14 +90,25 @@ describe('OdpManager', () => { const odpManagerInstance = (config?: OdpConfig) => new OdpManager({ - disable: false, - requestHandler, - eventManager, - segmentManager, + segmentLRUCache: new ServerLRUCache(), + segmentRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, + odpOptions: { + eventManager, + segmentManager, + }, }); it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { - const odpManager = new OdpManager({ disable: true, requestHandler, logger }); + const odpManager = new OdpManager({ + segmentLRUCache: new ServerLRUCache(), + segmentRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, + logger, + odpOptions: { + disabled: true, + }, + }); verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); odpManager.updateSettings(new OdpConfig('valid', 'host', [])); @@ -127,17 +140,20 @@ describe('OdpManager', () => { it('should use new settings in event manager when ODP Config is updated', async () => { const odpManager = new OdpManager({ - disable: false, - requestHandler, - eventManager: new OdpEventManager({ - odpConfig, - apiManager: eventApiManager, - logger, - clientEngine: '', - clientVersion: '', - batchSize: 1, - flushInterval: 250, - }), + segmentLRUCache: new ServerLRUCache(), + segmentRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, + odpOptions: { + eventManager: new OdpEventManager({ + odpConfig, + apiManager: eventApiManager, + logger, + clientEngine: '', + clientVersion: '', + batchSize: 1, + flushInterval: 250, + }), + }, }); odpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); @@ -160,26 +176,27 @@ describe('OdpManager', () => { it('should use new settings in segment manager when ODP Config is updated', async () => { const odpManager = new OdpManager({ - disable: false, - requestHandler, - segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), + segmentLRUCache: new ServerLRUCache(), + segmentRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, + odpOptions: { + segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), + }, }); odpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); expect(odpManager.odpConfig.apiKey).toBe(keyA); expect(odpManager.odpConfig.apiHost).toBe(hostA); - odpManager.fetchQualifiedSegments(userA); - await new Promise(resolve => setTimeout(resolve, 400)); + await odpManager.fetchQualifiedSegments(userA); verify(mockSegmentApiManager.fetchSegments(keyA, hostA, ODP_USER_KEY.FS_USER_ID, userA, anything())).once(); odpManager.updateSettings(new OdpConfig(keyB, hostB, segmentsB)); expect(odpManager.odpConfig.apiKey).toBe(keyB); expect(odpManager.odpConfig.apiHost).toBe(hostB); - odpManager.fetchQualifiedSegments(userB); - await new Promise(resolve => setTimeout(resolve, 400)); + await odpManager.fetchQualifiedSegments(userB); verify(mockSegmentApiManager.fetchSegments(keyB, hostB, ODP_USER_KEY.FS_USER_ID, userB, anything())).once(); }); @@ -188,8 +205,10 @@ describe('OdpManager', () => { expect(odpManagerA.eventManager).not.toBe(null); const odpManagerB = new OdpManager({ - disable: false, - requestHandler, + segmentLRUCache: new ServerLRUCache(), + segmentRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, + logger, }); expect(odpManagerB.eventManager).not.toBe(null); }); @@ -199,8 +218,9 @@ describe('OdpManager', () => { expect(odpManagerA.segmentManager).not.toBe(null); const odpManagerB = new OdpManager({ - disable: false, - requestHandler, + segmentLRUCache: new ServerLRUCache(), + segmentRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, }); expect(odpManagerB.eventManager).not.toBe(null); }); From 790e15486d4b56a728bee35281c7892855815252 Mon Sep 17 00:00:00 2001 From: Jae Kim <45045038+jaeopt@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:07:12 -0700 Subject: [PATCH 007/200] feat(ats): [FSSDK-8793] add support for disabling odp event batching (#809) --- .../lib/core/odp/odp_event_manager.ts | 8 +++++- .../tests/odpManager.browser.spec.ts | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 41478f08b..cfb9bf5ca 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -153,7 +153,13 @@ export class OdpEventManager implements IOdpEventManager { this.queueSize = queueSize || defaultQueueSize; this.batchSize = batchSize || DEFAULT_BATCH_SIZE; - this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; + if (flushInterval === 0) { + // disable event batching + this.batchSize = 1; + this.flushInterval = 0; + } else { + this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; + } this.state = STATE.STOPPED; } diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts index 313a0d069..78ddde011 100644 --- a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -444,6 +444,33 @@ describe('OdpManager', () => { expect(browserOdpManager.eventManager.flushInterval).toBe(4000); }); + it('Default ODP event flush interval is used when odpOptions does not include eventFlushInterval', () => { + const odpOptions: OdpOptions = {}; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.flushInterval).toBe(1000); + }); + + it('ODP event batch size set to one when odpOptions.eventFlushInterval set to 0', () => { + const odpOptions: OdpOptions = { + eventFlushInterval: 0, + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.flushInterval).toBe(0); + + // @ts-ignore + expect(browserOdpManager.eventManager.batchSize).toBe(1); + }); + it('Custom odpOptions.eventBatchSize overrides default Event Manager batch size', () => { const odpOptions: OdpOptions = { eventBatchSize: 2, From 70bdda499e0ff69b612d4fe34bc0c7d12cb9798c Mon Sep 17 00:00:00 2001 From: Jae Kim <45045038+jaeopt@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:27:34 -0700 Subject: [PATCH 008/200] feat(ats): [FSSDK-8690] fix configurable ODP request timeouts (#807) --- .../lib/plugins/odp_manager/index.browser.ts | 27 ++++++++++++++----- .../optimizely-sdk/lib/utils/enums/index.ts | 2 ++ .../tests/odpManager.browser.spec.ts | 19 +++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts index 76c1b34f4..858dd0a85 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts @@ -14,7 +14,14 @@ * limitations under the License. */ -import { BROWSER_CLIENT_VERSION, ERROR_MESSAGES, JAVASCRIPT_CLIENT_ENGINE, ODP_USER_KEY } from '../../utils/enums'; +import { + BROWSER_CLIENT_VERSION, + ERROR_MESSAGES, + JAVASCRIPT_CLIENT_ENGINE, + ODP_USER_KEY, + REQUEST_TIMEOUT_ODP_SEGMENTS_MS, + REQUEST_TIMEOUT_ODP_EVENTS_MS +} from '../../utils/enums'; import { getLogger, LoggerFacade, LogHandler, LogLevel } from '../../modules/logging'; import { BrowserRequestHandler } from './../../utils/http_request_handler/browser_request_handler'; @@ -49,16 +56,22 @@ export class BrowserOdpManager extends OdpManager { if (odpOptions?.segmentsRequestHandler) { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; - } else if (odpOptions?.segmentsApiTimeout) { - customSegmentRequestHandler = new BrowserRequestHandler(browserLogger, odpOptions.segmentsApiTimeout); + } else { + customSegmentRequestHandler = new BrowserRequestHandler( + browserLogger, + odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS + ); } let customEventRequestHandler; if (odpOptions?.eventRequestHandler) { customEventRequestHandler = odpOptions.eventRequestHandler; - } else if (odpOptions?.eventApiTimeout) { - customEventRequestHandler = new BrowserRequestHandler(browserLogger, odpOptions.eventApiTimeout); + } else { + customEventRequestHandler = new BrowserRequestHandler( + browserLogger, + odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS + ); } super({ @@ -68,8 +81,8 @@ export class BrowserOdpManager extends OdpManager { maxSize: odpOptions?.segmentsCacheSize, timeout: odpOptions?.segmentsCacheTimeout, }), - segmentRequestHandler: customSegmentRequestHandler || new BrowserRequestHandler(browserLogger), - eventRequestHandler: customEventRequestHandler || new BrowserRequestHandler(browserLogger), + segmentRequestHandler: customSegmentRequestHandler, + eventRequestHandler: customEventRequestHandler, logger: browserLogger, clientEngine: browserClientEngine, clientVersion: browserClientVersion, diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index 4f2e62aea..4ead22cb0 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -337,6 +337,8 @@ export enum NOTIFICATION_TYPES { * Default milliseconds before request timeout */ export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute +export const REQUEST_TIMEOUT_ODP_SEGMENTS_MS = 10 * 1000; // 10 secs +export const REQUEST_TIMEOUT_ODP_EVENTS_MS = 10 * 1000; // 10 secs /** * ODP User Key Options diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts index 78ddde011..3593ba7fb 100644 --- a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -336,6 +336,13 @@ describe('OdpManager', () => { expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4000); }); + it('Browser default Segments API Request Handler timeout should be used when odpOptions does not include segmentsApiTimeout', () => { + const browserOdpManager = new BrowserOdpManager({}); + + // @ts-ignore + expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(10000); + }); + it('Custom odpOptions.segmentsRequestHandler overrides default Segment API Request Handler', () => { const odpOptions: OdpOptions = { segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), @@ -431,6 +438,18 @@ describe('OdpManager', () => { expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(4000); }); + it('Browser default Events API Request Handler timeout should be used when odpOptions does not include eventsApiTimeout', () => { + const odpOptions: OdpOptions = { + }; + + const browserOdpManager = new BrowserOdpManager({ + odpOptions, + }); + + // @ts-ignore + expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(10000); + }); + it('Custom odpOptions.eventFlushInterval overrides default Event Manager flush interval', () => { const odpOptions: OdpOptions = { eventFlushInterval: 4000, From 8991d6e9ed1480c72e46ec89548ce086cd50eb8d Mon Sep 17 00:00:00 2001 From: Andy Leap <104936100+andrewleap-optimizely@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:19:03 -0400 Subject: [PATCH 009/200] [FSSDK-8993] feat: switch odp event rest endpoint (#808) * add support for odp browser event api * update browser odp event unit tests --- .../lib/core/odp/odp_event_api_manager.ts | 45 +++- .../lib/core/odp/odp_event_manager.ts | 49 ++-- .../optimizely-sdk/lib/core/odp/odp_utils.ts | 11 + .../optimizely-sdk/lib/index.browser.tests.js | 211 ++++++++++++------ .../optimizely-sdk/lib/optimizely/index.ts | 11 +- .../lib/plugins/odp_manager/index.browser.ts | 11 +- .../optimizely-sdk/lib/utils/enums/index.ts | 1 + .../tests/odpManager.browser.spec.ts | 16 +- 8 files changed, 243 insertions(+), 112 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts index 10f18ce32..b5b3c69a8 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,9 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { OdpEvent } from './odp_event'; +import { isBrowserContext } from './odp_utils'; import { RequestHandler } from '../../utils/http_request_handler/http'; +import { ODP_EVENT_BROWSER_ENDPOINT } from '../../utils/enums'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; @@ -33,6 +35,7 @@ export interface IOdpEventApiManager { export class OdpEventApiManager implements IOdpEventApiManager { private readonly logger: LogHandler; private readonly requestHandler: RequestHandler; + private readonly isBrowser: boolean; /** * Creates instance to access Optimizely Data Platform (ODP) REST API @@ -42,6 +45,7 @@ export class OdpEventApiManager implements IOdpEventApiManager { constructor(requestHandler: RequestHandler, logger: LogHandler) { this.requestHandler = requestHandler; this.logger = logger; + this.isBrowser = isBrowserContext() } /** @@ -64,14 +68,39 @@ export class OdpEventApiManager implements IOdpEventApiManager { return shouldRetry; } - const endpoint = `${apiHost}/v3/events`; - const data = JSON.stringify(events, this.replacer); + if (events.length > 1 && this.isBrowser) { + this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (browser only supports batch size 1)`); + return shouldRetry; + } + + let method, endpoint, headers, data; + + if (this.isBrowser) { + method = 'GET'; + const event = events[0]; + const url = new URL(ODP_EVENT_BROWSER_ENDPOINT); + event.identifiers.forEach((v, k) =>{ + url.searchParams.append(k, v); + }); + event.data.forEach((v, k) =>{ + url.searchParams.append(k, v as string); + }); + url.searchParams.append('tracker_id', apiKey); + url.searchParams.append('event_type', event.type); + url.searchParams.append('vdl_action', event.action); + endpoint = url.toString(); + headers = {}; + } else { + method = 'POST'; + endpoint = `${apiHost}/v3/events`; + headers = { + 'Content-Type': 'application/json', + 'x-api-key': apiKey, + }; + data = JSON.stringify(events, this.replacer); + } + - const method = 'POST'; - const headers = { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - }; let statusCode = 0; try { diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index cfb9bf5ca..249a772ca 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -22,7 +22,7 @@ import { ERROR_MESSAGES, ODP_USER_KEY, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION import { OdpEvent } from './odp_event'; import { OdpConfig } from './odp_config'; import { OdpEventApiManager } from './odp_event_api_manager'; -import { invalidOdpDataFound } from './odp_utils'; +import { invalidOdpDataFound, isBrowserContext } from './odp_utils'; const MAX_RETRIES = 3; const DEFAULT_BATCH_SIZE = 10; @@ -54,6 +54,8 @@ export interface IOdpEventManager { identifyUser(userId: string, vuid?: string): void; sendEvent(event: OdpEvent): void; + + flush(): void; } /** @@ -95,12 +97,12 @@ export class OdpEventManager implements IOdpEventManager { */ private readonly queueSize: number; /** - * Maximum number of events to process at once + * Maximum number of events to process at once. Ignored in browser context * @private */ private readonly batchSize: number; /** - * Milliseconds between setTimeout() to process new batches + * Milliseconds between setTimeout() to process new batches. Ignored in browser context * @private */ private readonly flushInterval: number; @@ -140,20 +142,13 @@ export class OdpEventManager implements IOdpEventManager { this.clientEngine = clientEngine; this.clientVersion = clientVersion; - let defaultQueueSize = DEFAULT_BROWSER_QUEUE_SIZE; - - try { - // TODO: Consider refactoring to use typeof process and combine w/above line - if (process) { - defaultQueueSize = DEFAULT_SERVER_QUEUE_SIZE; - } - } catch (e) { - // TODO: Create Browser and Non-Browser specific variants of ODP Event Manager to avoid this try/catch - } + const isBrowser = isBrowserContext(); + const defaultQueueSize = isBrowser ? DEFAULT_BROWSER_QUEUE_SIZE : DEFAULT_SERVER_QUEUE_SIZE; this.queueSize = queueSize || defaultQueueSize; this.batchSize = batchSize || DEFAULT_BATCH_SIZE; - if (flushInterval === 0) { + + if (flushInterval === 0 || isBrowser) { // disable event batching this.batchSize = 1; this.flushInterval = 0; @@ -161,6 +156,15 @@ export class OdpEventManager implements IOdpEventManager { this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; } + if (isBrowser) { + if (typeof batchSize !== 'undefined' && batchSize !== 1) { + this.logger.log(LogLevel.WARNING, 'ODP event batch size must be 1 in the browser.'); + } + if (typeof flushInterval !== 'undefined' && flushInterval !== 0) { + this.logger.log(LogLevel.WARNING, 'ODP event flush interval must be 0 in the browser.'); + } + } + this.state = STATE.STOPPED; } @@ -398,17 +402,12 @@ export class OdpEventManager implements IOdpEventManager { return true; } - try { - if (process) { - // if Node/server-side context, empty queue items before ready state - this.logger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); - this.queue = new Array(); - } else { - // in Browser/client-side context, give debug message but leave events in queue - this.logger.log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); - } - } catch (e) { - // TODO: Create Browser and Non-Browser specific variants of ODP Event Manager to avoid this try/catch + if (!isBrowserContext()) { + // if Node/server-side context, empty queue items before ready state + this.logger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); + this.queue = new Array(); + } else { + // in Browser/client-side context, give debug message but leave events in queue this.logger.log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_utils.ts b/packages/optimizely-sdk/lib/core/odp/odp_utils.ts index 875b7e091..e559ba451 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_utils.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_utils.ts @@ -30,3 +30,14 @@ export function invalidOdpDataFound(data: Map): boolean { }); return foundInvalidValue; } + +/** + * Determine if the runtime environment is a browser + * @returns True if in the browser + * @private + */ +export function isBrowserContext(): boolean { + return !(typeof process !== "undefined" && + process.versions != null && + process.versions.node != null); +} diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index ec1400959..f4e3ce4da 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -25,9 +25,12 @@ import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import OptimizelyUserContext from './optimizely_user_context'; -import { LOG_MESSAGES, ODP_EVENT_ACTION } from './utils/enums'; +import { LOG_MESSAGES, ODP_EVENT_ACTION, ODP_EVENT_BROWSER_ENDPOINT } from './utils/enums'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import { OdpConfig } from './core/odp/odp_config'; +import { OdpEventManager } from './core/odp/odp_event_manager'; +import { OdpEventApiManager } from './core/odp/odp_event_api_manager'; +import { isBrowserContext } from './core/odp/odp_utils'; var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; @@ -582,14 +585,29 @@ describe('javascript-sdk (Browser)', function() { const testFsUserId = 'fs_test_user'; const testVuid = 'vuid_test_user'; + var clock; + const requestParams = new Map; + const mockRequestHandler = { + makeRequest: (endpoint, headers, method, data) => { + requestParams.set('endpoint', endpoint); + requestParams.set('headers', headers); + requestParams.set('method', method); + requestParams.set('data', data); + return {responsePromise: (async()=>{return {statusCode: 200}})()} + }, + args: requestParams + }; beforeEach(function() { sandbox.stub(logger, 'log'); sandbox.stub(logger, 'error'); + clock = sinon.useFakeTimers(new Date()); }); afterEach(function() { sandbox.restore(); + clock.restore() + requestParams.clear() }); it('should send identify event by default when initialized', () => { @@ -672,13 +690,10 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ - logger, - odpOptions: { - segmentsCacheSize: 10, - segmentsCacheTimeout: 10, - }, - }), + odpOptions: { + segmentsCacheSize: 10, + segmentsCacheTimeout: 10, + }, }); sinon.assert.calledWith( @@ -694,7 +709,6 @@ describe('javascript-sdk (Browser)', function() { ); }); - // TODO: Patch VUID Promise Pattern (@Andy) it('should accept a valid custom odp segment manager', async () => { const fakeSegmentManager = { fetchQualifiedSegments: sinon.spy(), @@ -707,29 +721,25 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ - logger, - odpOptions: { - segmentManager: fakeSegmentManager, - }, - }), + odpOptions: { + segmentManager: fakeSegmentManager, + }, }); sinon.assert.called(fakeSegmentManager.updateSettings); - try { - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isEmpty(readyData.reason); + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); - await client.fetchQualifiedSegments(testVuid); + await client.fetchQualifiedSegments(testVuid); + + sinon.assert.notCalled(logger.error); + sinon.assert.called(fakeSegmentManager.fetchQualifiedSegments); - sinon.assert.notCalled(logger.error); - sinon.assert.called(fakeSegmentManager.fetchQualifiedSegments); - } catch (e) {} }); - it('should accept a valid custom odp event manager', () => { + it('should accept a valid custom odp event manager', async () => { const fakeEventManager = { start: sinon.spy(), updateSettings: sinon.spy(), @@ -746,49 +756,51 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ - logger, - odpOptions: { - eventManager: fakeEventManager, - }, - }), + odpOptions: { + disabled: false, + eventManager: fakeEventManager, + } }); + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); sinon.assert.called(fakeEventManager.start); }); - // TODO: Patch VUID Promise Pattern (@Andy) it('should send an odp event with sendOdpEvent', async () => { - const fakeOdpManager = { - sendEvent: sinon.spy(), + const fakeEventManager = { updateSettings: sinon.spy(), + start: sinon.spy(), + stop: sinon.spy(), + registerVuid: sinon.spy(), identifyUser: sinon.spy(), - close: sinon.spy(), + sendEvent: sinon.spy(), + flush: sinon.spy(), }; const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + datafile: testData.getOdpIntegratedConfigWithSegments(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: fakeOdpManager, + odpOptions: { + eventManager: fakeEventManager + } }); - try { - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isEmpty(readyData.reason); - client.sendOdpEvent({ - action: ODP_EVENT_ACTION.INITIALIZED, - }); + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + await client.sendOdpEvent({ + action: ODP_EVENT_ACTION.INITIALIZED, + }); - sinon.assert.notCalled(logger.error); - sinon.assert.called(fakeOdpManager.sendEvent); - } catch (e) {} + sinon.assert.notCalled(logger.error); + sinon.assert.called(fakeEventManager.sendEvent); }); - // TODO: Patch VUID Promise Pattern (@Andy) it('should log an error when attempting to send an odp event when odp is disabled', async () => { const client = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), @@ -796,25 +808,98 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ - logger, - odpOptions: { - disabled: true, - }, - }), + odpOptions: { + disabled: true, + }, }); - try { - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isEmpty(readyData.reason); - client.sendOdpEvent({ - action: ODP_EVENT_ACTION.INITIALIZED, - }); + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + await client.sendOdpEvent({ + action: ODP_EVENT_ACTION.INITIALIZED, + }); + + sinon.assert.calledWith(logger.error, 'ODP event send failed.'); + sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); + }); + + it('should log a warning when attempting to use an event batch size other than 1', async () => { + if (!isBrowserContext()) { + return + } + const client = optimizelyFactory.createInstance({ + datafile: testData.getOdpIntegratedConfigWithSegments(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpOptions: { + eventBatchSize: 5 + }, + }); + + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + await client.sendOdpEvent({ + action: ODP_EVENT_ACTION.INITIALIZED, + }); + + sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.WARNING, 'ODP event batch size must be 1 in the browser.'); + assert(client.odpManager.eventManager.batchSize, 1); + }); + + it('should send an odp event to the browser endpoint', async () => { + if (!isBrowserContext()) { + return + } + const odpConfig = new OdpConfig(); + const apiManager = new OdpEventApiManager(mockRequestHandler, logger); + const eventManager = new OdpEventManager({odpConfig, apiManager, logger, clientEngine: "javascript-sdk", clientVersion: "great" }) + + let datafile = testData.getOdpIntegratedConfigWithSegments(); + + const client = optimizelyFactory.createInstance({ + datafile, + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpOptions: { + odpConfig, + eventManager + } + }); + + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + await client.sendOdpEvent({ + action: ODP_EVENT_ACTION.INITIALIZED, + }); + + // wait for request to be sent + clock.tick(100); + + let publicKey = datafile.integrations[0].publicKey; + + let requestEndpoint = new URL(requestParams.get('endpoint')); + assert.equal(requestEndpoint.origin + requestEndpoint.pathname, ODP_EVENT_BROWSER_ENDPOINT) + assert.equal(requestParams.get('method'), 'GET') + + let searchParams = requestEndpoint.searchParams; + assert.lengthOf(searchParams.get('idempotence_id'), 36); + assert.equal(searchParams.get('data_source'), 'javascript-sdk'); + assert.equal(searchParams.get('data_source_type'), 'sdk'); + assert.equal(searchParams.get('data_source_version'), 'great'); + assert.equal(searchParams.get('tracker_id'), publicKey); + assert.equal(searchParams.get('event_type'), 'fullstack'); + assert.equal(searchParams.get('vdl_action'), ODP_EVENT_ACTION.INITIALIZED); + assert.isTrue(searchParams.get('vuid').startsWith('vuid_')); + assert.isNotNull(searchParams.get('data_source_version')); - sinon.assert.calledWith(logger.error, 'ODP event send failed.'); - sinon.assert.calledWith(logger.error, 'ODP is not enabled.'); - } catch (e) {} + sinon.assert.notCalled(logger.error); }); }); }); diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 69714e096..776d13a89 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1668,7 +1668,7 @@ export default class Optimizely { * @param {Map} odpEvent.identifiers (Optional) Key-value map of user identifiers * @param {Map} odpEvent.data (Optional) Event data in a key-value map. */ - public sendOdpEvent({ + public async sendOdpEvent({ type, action, identifiers, @@ -1678,16 +1678,17 @@ export default class Optimizely { action: ODP_EVENT_ACTION; identifiers?: Map; data?: Map; - }): void { + }): Promise { if (!this.odpManager) { this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING); + return; } const odpEventType = type ?? ODP_DEFAULT_EVENT_TYPE; try { const odpEvent = new OdpEvent(odpEventType, action, identifiers, data); - this.odpManager!.sendEvent(odpEvent); + await this.odpManager.sendEvent(odpEvent); } catch (e) { this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED, e); } @@ -1697,9 +1698,9 @@ export default class Optimizely { * Identifies user with ODP server in a fire-and-forget manner. * @param {string} userId */ - public identifyUser(userId: string): void { + public async identifyUser(userId: string): Promise { if (this.odpManager && this.odpManager.enabled) { - this.odpManager.identifyUser(userId); + await this.odpManager.identifyUser(userId); } } diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts index 858dd0a85..028fe5430 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts @@ -45,6 +45,7 @@ interface BrowserOdpManagerConfig { export class BrowserOdpManager extends OdpManager { static cache = new BrowserAsyncStorageCache(); vuid?: string; + initPromise?: Promise constructor({ logger, odpOptions }: BrowserOdpManagerConfig) { const browserLogger = logger || getLogger('BrowserOdpManager'); @@ -90,7 +91,9 @@ export class BrowserOdpManager extends OdpManager { }); this.logger = browserLogger; - this.initializeVuid(BrowserOdpManager.cache); + this.initPromise = this.initializeVuid(BrowserOdpManager.cache).catch(e => { + this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, e); + }); } /** @@ -121,7 +124,8 @@ export class BrowserOdpManager extends OdpManager { * - Additionally, also passes VUID to help identify client-side users * @param fsUserId Unique identifier of a target user. */ - public identifyUser(fsUserId?: string, vuid?: string): void { + public async identifyUser(fsUserId?: string, vuid?: string): Promise { + await this.initPromise; if (fsUserId && VuidManager.isVuid(fsUserId)) { super.identifyUser(undefined, fsUserId); return; @@ -142,7 +146,8 @@ export class BrowserOdpManager extends OdpManager { * - Identifiers must contain at least one key-value pair * @param {OdpEvent} odpEvent > ODP Event to send to event manager */ - public sendEvent({ type, action, identifiers, data }: OdpEvent): void { + public async sendEvent({ type, action, identifiers, data }: OdpEvent): Promise { + await this.initPromise; const identifiersWithVuid = new Map(identifiers); if (!identifiers.has(ODP_USER_KEY.VUID)) { diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index 4ead22cb0..393a07028 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -349,6 +349,7 @@ export enum ODP_USER_KEY { } export const ODP_DEFAULT_EVENT_TYPE = 'fullstack'; +export const ODP_EVENT_BROWSER_ENDPOINT = 'https://jumbe.zaius.com/v2/zaius.gif'; /** * ODP Event Action Options diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts index 3593ba7fb..17514d890 100644 --- a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -126,7 +126,7 @@ describe('OdpManager', () => { await browserOdpManager.fetchQualifiedSegments('vuid_user1', []); verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); - browserOdpManager.identifyUser('vuid_user1'); + await browserOdpManager.identifyUser('vuid_user1'); verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); expect(browserOdpManager.eventManager).toBeUndefined; @@ -172,7 +172,7 @@ describe('OdpManager', () => { const updateSettingsArgsA = capture(mockEventManager.updateSettings).last(); expect(updateSettingsArgsA[0]).toStrictEqual(odpConfigA); - browserOdpManager.identifyUser(userA); + await browserOdpManager.identifyUser(userA); const identifyUserArgsA = capture(mockEventManager.identifyUser).last(); expect(identifyUserArgsA[0]).toStrictEqual(userA); @@ -183,7 +183,7 @@ describe('OdpManager', () => { const updateSettingsArgsB = capture(mockEventManager.updateSettings).last(); expect(updateSettingsArgsB[0]).toStrictEqual(odpConfigB); - browserOdpManager.eventManager!.identifyUser(userB); + await browserOdpManager.eventManager!.identifyUser(userB); const identifyUserArgsB = capture(mockEventManager.identifyUser).last(); expect(identifyUserArgsB[0]).toStrictEqual(userB); }); @@ -236,7 +236,7 @@ describe('OdpManager', () => { expect(browserOdpManagerB.eventManager).not.toBe(null); }); - it("should call event manager's sendEvent if ODP Event is valid", () => { + it("should call event manager's sendEvent if ODP Event is valid", async () => { const browserOdpManager = new BrowserOdpManager({ odpOptions: { eventManager: fakeEventManager, @@ -253,16 +253,16 @@ describe('OdpManager', () => { const validOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, validIdentifiers); - browserOdpManager.sendEvent(validOdpEvent); + await browserOdpManager.sendEvent(validOdpEvent); verify(mockEventManager.sendEvent(anything())).once(); // Test Invalid OdpEvents - logs error and short circuits // Does not include `vuid` in identifiers does not have a local this.vuid populated in BrowserOdpManager + browserOdpManager.vuid = undefined; const invalidOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, undefined); - expect(() => { - browserOdpManager.sendEvent(invalidOdpEvent); - }).toThrow(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING); + await expect(browserOdpManager.sendEvent(invalidOdpEvent)) + .rejects.toThrow(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING) }); describe('Populates BrowserOdpManager correctly with all odpOptions', () => { From 2580ea0e097d243db7050635992ebb1e7802e2ff Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Tue, 11 Apr 2023 14:10:29 -0400 Subject: [PATCH 010/200] fix(ats): [FSSDK-9023] Fix ODP Exports (#812) Consolidated & patched external-facing interfaces for FSC testing. Additionally, also refactored internal implementations of Browser/Node-specific implementations to avoid using unnecessary implicit contextual checks. --- .../lib/core/odp/odp_event_api_manager.ts | 55 +- .../lib/core/odp/odp_event_manager.ts | 68 +- .../lib/core/odp/odp_manager.ts | 88 +- .../optimizely-sdk/lib/core/odp/odp_utils.ts | 11 - packages/optimizely-sdk/lib/export_types.ts | 6 + .../optimizely-sdk/lib/index.browser.tests.js | 78 +- packages/optimizely-sdk/lib/index.browser.ts | 4 +- packages/optimizely-sdk/lib/index.node.ts | 27 +- .../lib/optimizely/index.browser.ts | 62 - .../lib/optimizely/index.tests.js | 3 +- .../optimizely-sdk/lib/optimizely/index.ts | 122 +- .../lib/optimizely_user_context/index.ts | 19 +- .../odp/event_api_manager/index.browser.ts | 38 + .../odp/event_api_manager/index.node.ts | 28 + .../odp/event_manager/index.browser.ts | 27 + .../plugins/odp/event_manager/index.node.ts | 29 + .../lib/plugins/odp_manager/index.browser.ts | 100 +- .../lib/plugins/odp_manager/index.node.ts | 104 +- packages/optimizely-sdk/lib/shared_types.ts | 43 +- .../lib/utils/lru_cache/server_lru_cache.ts | 2 +- packages/optimizely-sdk/package-lock.json | 38978 +++------------- .../tests/odpEventApiManager.spec.ts | 8 +- .../tests/odpEventManager.spec.ts | 5 +- .../tests/odpManager.browser.spec.ts | 84 +- .../optimizely-sdk/tests/odpManager.spec.ts | 41 +- 25 files changed, 6130 insertions(+), 33900 deletions(-) delete mode 100644 packages/optimizely-sdk/lib/optimizely/index.browser.ts create mode 100644 packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts create mode 100644 packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts create mode 100644 packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts create mode 100644 packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts index b5b3c69a8..d067855e1 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts @@ -16,9 +16,7 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { OdpEvent } from './odp_event'; -import { isBrowserContext } from './odp_utils'; import { RequestHandler } from '../../utils/http_request_handler/http'; -import { ODP_EVENT_BROWSER_ENDPOINT } from '../../utils/enums'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; @@ -32,10 +30,9 @@ export interface IOdpEventApiManager { /** * Concrete implementation for accessing the ODP REST API */ -export class OdpEventApiManager implements IOdpEventApiManager { +export abstract class OdpEventApiManager implements IOdpEventApiManager { private readonly logger: LogHandler; private readonly requestHandler: RequestHandler; - private readonly isBrowser: boolean; /** * Creates instance to access Optimizely Data Platform (ODP) REST API @@ -45,7 +42,10 @@ export class OdpEventApiManager implements IOdpEventApiManager { constructor(requestHandler: RequestHandler, logger: LogHandler) { this.requestHandler = requestHandler; this.logger = logger; - this.isBrowser = isBrowserContext() + } + + public getLogger(): LogHandler { + return this.logger; } /** @@ -68,39 +68,11 @@ export class OdpEventApiManager implements IOdpEventApiManager { return shouldRetry; } - if (events.length > 1 && this.isBrowser) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (browser only supports batch size 1)`); + if (!this.shouldSendEvents(events)) { return shouldRetry; } - let method, endpoint, headers, data; - - if (this.isBrowser) { - method = 'GET'; - const event = events[0]; - const url = new URL(ODP_EVENT_BROWSER_ENDPOINT); - event.identifiers.forEach((v, k) =>{ - url.searchParams.append(k, v); - }); - event.data.forEach((v, k) =>{ - url.searchParams.append(k, v as string); - }); - url.searchParams.append('tracker_id', apiKey); - url.searchParams.append('event_type', event.type); - url.searchParams.append('vdl_action', event.action); - endpoint = url.toString(); - headers = {}; - } else { - method = 'POST'; - endpoint = `${apiHost}/v3/events`; - headers = { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - }; - data = JSON.stringify(events, this.replacer); - } - - + const { method, endpoint, headers, data } = this.generateRequestData(apiHost, apiKey, events); let statusCode = 0; try { @@ -127,11 +99,12 @@ export class OdpEventApiManager implements IOdpEventApiManager { return shouldRetry; } - private replacer(_: unknown, value: unknown) { - if (value instanceof Map) { - return Object.fromEntries(value); - } else { - return value; - } + protected abstract shouldSendEvents(events: OdpEvent[]): boolean; + + protected abstract generateRequestData(apiHost: string, apiKey: string, events: OdpEvent[]): { + method: string, + endpoint: string, + headers: {[key: string]: string}, + data: string, } } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 249a772ca..ed1457d0a 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -22,7 +22,7 @@ import { ERROR_MESSAGES, ODP_USER_KEY, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION import { OdpEvent } from './odp_event'; import { OdpConfig } from './odp_config'; import { OdpEventApiManager } from './odp_event_api_manager'; -import { invalidOdpDataFound, isBrowserContext } from './odp_utils'; +import { invalidOdpDataFound } from './odp_utils'; const MAX_RETRIES = 3; const DEFAULT_BATCH_SIZE = 10; @@ -61,16 +61,16 @@ export interface IOdpEventManager { /** * Concrete implementation of a manager for persisting events to the Optimizely Data Platform */ -export class OdpEventManager implements IOdpEventManager { +export abstract class OdpEventManager implements IOdpEventManager { /** * Current state of the event processor */ public state: STATE = STATE.STOPPED; /** * Queue for holding all events to be eventually dispatched - * @private + * @protected */ - private queue = new Array(); + protected queue = new Array(); /** * Identifier of the currently running timeout so clearCurrentTimeout() can be called * @private @@ -93,19 +93,19 @@ export class OdpEventManager implements IOdpEventManager { private readonly logger: LogHandler; /** * Maximum queue size - * @private + * @protected */ - private readonly queueSize: number; + protected queueSize!: number; /** * Maximum number of events to process at once. Ignored in browser context - * @private + * @protected */ - private readonly batchSize: number; + protected batchSize!: number; /** * Milliseconds between setTimeout() to process new batches. Ignored in browser context - * @private + * @protected */ - private readonly flushInterval: number; + protected flushInterval!: number; /** * Type of execution context eg node, js, react * @private @@ -141,33 +141,16 @@ export class OdpEventManager implements IOdpEventManager { this.logger = logger; this.clientEngine = clientEngine; this.clientVersion = clientVersion; - - const isBrowser = isBrowserContext(); - - const defaultQueueSize = isBrowser ? DEFAULT_BROWSER_QUEUE_SIZE : DEFAULT_SERVER_QUEUE_SIZE; - this.queueSize = queueSize || defaultQueueSize; - this.batchSize = batchSize || DEFAULT_BATCH_SIZE; - - if (flushInterval === 0 || isBrowser) { - // disable event batching - this.batchSize = 1; - this.flushInterval = 0; - } else { - this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; - } - - if (isBrowser) { - if (typeof batchSize !== 'undefined' && batchSize !== 1) { - this.logger.log(LogLevel.WARNING, 'ODP event batch size must be 1 in the browser.'); - } - if (typeof flushInterval !== 'undefined' && flushInterval !== 0) { - this.logger.log(LogLevel.WARNING, 'ODP event flush interval must be 0 in the browser.'); - } - } - + this.initParams(batchSize, queueSize, flushInterval); this.state = STATE.STOPPED; } + protected abstract initParams( + batchSize: number | undefined, + queueSize: number | undefined, + flushInterval: number | undefined, + ): void; + /** * Update ODP configuration settings. * @param newConfig New configuration to apply @@ -401,19 +384,12 @@ export class OdpEventManager implements IOdpEventManager { if (this.odpConfig.isReady()) { return true; } - - if (!isBrowserContext()) { - // if Node/server-side context, empty queue items before ready state - this.logger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); - this.queue = new Array(); - } else { - // in Browser/client-side context, give debug message but leave events in queue - this.logger.log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); - } - + this.discardEventsIfNeeded(); return false; } + protected abstract discardEventsIfNeeded(): void; + /** * Add additional common data including an idempotent ID and execution context to event data * @param sourceData Existing event data to augment @@ -430,4 +406,8 @@ export class OdpEventManager implements IOdpEventManager { sourceData.forEach((value, key) => data.set(key, value)); return data; } + + protected getLogger(): LogHandler { + return this.logger; + } } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts index 002405968..8420613d2 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts @@ -14,51 +14,26 @@ * limitations under the License. */ -import { BROWSER_CLIENT_VERSION, LOG_MESSAGES } from './../../utils/enums/index'; +import { LOG_MESSAGES } from './../../utils/enums/index'; import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; -import { RequestHandler } from './../../utils/http_request_handler/http'; - -import { LRUCache } from './../../utils/lru_cache/lru_cache'; - import { VuidManager } from '../../plugins/vuid_manager'; import { OdpConfig } from './odp_config'; import { OdpEventManager } from './odp_event_manager'; import { OdpSegmentManager } from './odp_segment_manager'; -import { OdpSegmentApiManager } from './odp_segment_api_manager'; -import { OdpEventApiManager } from './odp_event_api_manager'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { invalidOdpDataFound } from './odp_utils'; import { OdpEvent } from './odp_event'; -import { OdpOptions } from '../../shared_types'; - -/** - * @param {LRUCache[]} segmentLRUCache Cache to be used for storing segments. - * @param {RequestHandler} segmentRequestHandler HTTP request handler that will be used by the ODP Segment Manager. - * @param {RequestHandler} eventRequestHandler HTTP request handler that will be used by the ODP Event Manager. - * @param {LogHandler} logger (Optional) Accepts custom LogHandler. Defaults to the default global LogHandler. - * @param {string} clientEngine (Optional) String denoting specific client engine being used. Defaults to 'javascript-sdk'. - * @param {string} clientVersion (Optional) String denoting specific client version. Defaults to current version value from package.json. - * @param {OdpOptions} odpOptions (Optional) Configuration settings for various ODP options from segment cache size to event flush interval. - */ -interface OdpManagerConfig { - segmentLRUCache: LRUCache; - segmentRequestHandler: RequestHandler; - eventRequestHandler: RequestHandler; - logger?: LogHandler; - clientEngine?: string; - clientVersion?: string; - odpOptions?: OdpOptions; -} /** * Orchestrates segments manager, event manager, and ODP configuration */ -export class OdpManager { - enabled: boolean; - logger: LogHandler; +export abstract class OdpManager { + initPromise?: Promise; + enabled = true; + logger: LogHandler = getLogger(); odpConfig: OdpConfig = new OdpConfig(); /** @@ -73,54 +48,7 @@ export class OdpManager { */ public eventManager: OdpEventManager | undefined; - constructor({ - segmentLRUCache, - segmentRequestHandler, - eventRequestHandler, - logger, - clientEngine, - clientVersion, - odpOptions, - }: OdpManagerConfig) { - this.enabled = !odpOptions?.disabled; - this.logger = logger || getLogger(); - - if (!this.enabled) { - this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); - return; - } - - // Set up Segment Manager (Audience Segments GraphQL API Interface) - if (odpOptions?.segmentManager) { - this.segmentManager = odpOptions.segmentManager; - this.segmentManager.updateSettings(this.odpConfig); - } else { - this.segmentManager = new OdpSegmentManager( - this.odpConfig, - segmentLRUCache, - new OdpSegmentApiManager(segmentRequestHandler, this.logger) - ); - } - - // Set up Events Manager (Events REST API Interface) - if (odpOptions?.eventManager) { - this.eventManager = odpOptions.eventManager; - this.eventManager.updateSettings(this.odpConfig); - } else { - this.eventManager = new OdpEventManager({ - odpConfig: this.odpConfig, - apiManager: new OdpEventApiManager(eventRequestHandler, this.logger), - logger: this.logger, - clientEngine: clientEngine || 'javascript-sdk', - clientVersion: clientVersion || BROWSER_CLIENT_VERSION, - flushInterval: odpOptions?.eventFlushInterval, - batchSize: odpOptions?.eventBatchSize, - queueSize: odpOptions?.eventQueueSize, - }); - } - - this.eventManager.start(); - } + constructor() {} /** * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments @@ -246,4 +174,8 @@ export class OdpManager { this.eventManager.sendEvent(new OdpEvent(type, action, identifiers, data)); } + + public abstract isVuidEnabled(): boolean; + + public abstract getVuid(): string | undefined; } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_utils.ts b/packages/optimizely-sdk/lib/core/odp/odp_utils.ts index e559ba451..875b7e091 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_utils.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_utils.ts @@ -30,14 +30,3 @@ export function invalidOdpDataFound(data: Map): boolean { }); return foundInvalidValue; } - -/** - * Determine if the runtime environment is a browser - * @returns True if in the browser - * @private - */ -export function isBrowserContext(): boolean { - return !(typeof process !== "undefined" && - process.versions != null && - process.versions.node != null); -} diff --git a/packages/optimizely-sdk/lib/export_types.ts b/packages/optimizely-sdk/lib/export_types.ts index 46e89c321..1903b2604 100644 --- a/packages/optimizely-sdk/lib/export_types.ts +++ b/packages/optimizely-sdk/lib/export_types.ts @@ -13,6 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * This file contains a collection of all types to be externally exported. + */ + export { UserAttributes, OptimizelyConfig, @@ -38,4 +43,5 @@ export { ActivateListenerPayload, TrackListenerPayload, NotificationCenter, + OptimizelySegmentOption, } from './shared_types'; diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index f4e3ce4da..eb4cc9e2d 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -28,9 +28,8 @@ import OptimizelyUserContext from './optimizely_user_context'; import { LOG_MESSAGES, ODP_EVENT_ACTION, ODP_EVENT_BROWSER_ENDPOINT } from './utils/enums'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import { OdpConfig } from './core/odp/odp_config'; -import { OdpEventManager } from './core/odp/odp_event_manager'; -import { OdpEventApiManager } from './core/odp/odp_event_api_manager'; -import { isBrowserContext } from './core/odp/odp_utils'; +import { BrowserOdpEventManager } from './plugins/odp/event_manager/index.browser'; +import { BrowserOdpEventApiManager } from './plugins/odp/event_api_manager/index.browser'; var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; @@ -586,16 +585,20 @@ describe('javascript-sdk (Browser)', function() { const testFsUserId = 'fs_test_user'; const testVuid = 'vuid_test_user'; var clock; - const requestParams = new Map; + const requestParams = new Map(); const mockRequestHandler = { makeRequest: (endpoint, headers, method, data) => { requestParams.set('endpoint', endpoint); requestParams.set('headers', headers); requestParams.set('method', method); requestParams.set('data', data); - return {responsePromise: (async()=>{return {statusCode: 200}})()} + return { + responsePromise: (async () => { + return { statusCode: 200 }; + })(), + }; }, - args: requestParams + args: requestParams, }; beforeEach(function() { @@ -606,8 +609,8 @@ describe('javascript-sdk (Browser)', function() { afterEach(function() { sandbox.restore(); - clock.restore() - requestParams.clear() + clock.restore(); + requestParams.clear(); }); it('should send identify event by default when initialized', () => { @@ -736,7 +739,6 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.notCalled(logger.error); sinon.assert.called(fakeSegmentManager.fetchQualifiedSegments); - }); it('should accept a valid custom odp event manager', async () => { @@ -759,7 +761,7 @@ describe('javascript-sdk (Browser)', function() { odpOptions: { disabled: false, eventManager: fakeEventManager, - } + }, }); const readyData = await client.onReady(); assert.equal(readyData.success, true); @@ -786,16 +788,15 @@ describe('javascript-sdk (Browser)', function() { eventBatchSize: null, logger, odpOptions: { - eventManager: fakeEventManager - } + eventManager: fakeEventManager, + }, }); const readyData = await client.onReady(); assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); - await client.sendOdpEvent({ - action: ODP_EVENT_ACTION.INITIALIZED, - }); + + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); sinon.assert.notCalled(logger.error); sinon.assert.called(fakeEventManager.sendEvent); @@ -816,18 +817,14 @@ describe('javascript-sdk (Browser)', function() { const readyData = await client.onReady(); assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); - await client.sendOdpEvent({ - action: ODP_EVENT_ACTION.INITIALIZED, - }); + + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); sinon.assert.calledWith(logger.error, 'ODP event send failed.'); sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); }); it('should log a warning when attempting to use an event batch size other than 1', async () => { - if (!isBrowserContext()) { - return - } const client = optimizelyFactory.createInstance({ datafile: testData.getOdpIntegratedConfigWithSegments(), errorHandler: fakeErrorHandler, @@ -835,28 +832,34 @@ describe('javascript-sdk (Browser)', function() { eventBatchSize: null, logger, odpOptions: { - eventBatchSize: 5 + eventBatchSize: 5, }, }); const readyData = await client.onReady(); assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); - await client.sendOdpEvent({ - action: ODP_EVENT_ACTION.INITIALIZED, - }); - sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.WARNING, 'ODP event batch size must be 1 in the browser.'); + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); + + sinon.assert.calledWith( + logger.log, + optimizelyFactory.enums.LOG_LEVEL.WARNING, + 'ODP event batch size must be 1 in the browser.' + ); assert(client.odpManager.eventManager.batchSize, 1); }); it('should send an odp event to the browser endpoint', async () => { - if (!isBrowserContext()) { - return - } const odpConfig = new OdpConfig(); - const apiManager = new OdpEventApiManager(mockRequestHandler, logger); - const eventManager = new OdpEventManager({odpConfig, apiManager, logger, clientEngine: "javascript-sdk", clientVersion: "great" }) + const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); + const eventManager = new BrowserOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine: 'javascript-sdk', + clientVersion: 'great', + }); let datafile = testData.getOdpIntegratedConfigWithSegments(); @@ -868,16 +871,15 @@ describe('javascript-sdk (Browser)', function() { logger, odpOptions: { odpConfig, - eventManager - } + eventManager, + }, }); const readyData = await client.onReady(); assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); - await client.sendOdpEvent({ - action: ODP_EVENT_ACTION.INITIALIZED, - }); + + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); // wait for request to be sent clock.tick(100); @@ -885,8 +887,8 @@ describe('javascript-sdk (Browser)', function() { let publicKey = datafile.integrations[0].publicKey; let requestEndpoint = new URL(requestParams.get('endpoint')); - assert.equal(requestEndpoint.origin + requestEndpoint.pathname, ODP_EVENT_BROWSER_ENDPOINT) - assert.equal(requestParams.get('method'), 'GET') + assert.equal(requestEndpoint.origin + requestEndpoint.pathname, ODP_EVENT_BROWSER_ENDPOINT); + assert.equal(requestParams.get('method'), 'GET'); let searchParams = requestEndpoint.searchParams; assert.lengthOf(searchParams.get('idempotence_id'), 36); diff --git a/packages/optimizely-sdk/lib/index.browser.ts b/packages/optimizely-sdk/lib/index.browser.ts index 153e86ed1..39f7ccde5 100644 --- a/packages/optimizely-sdk/lib/index.browser.ts +++ b/packages/optimizely-sdk/lib/index.browser.ts @@ -27,7 +27,7 @@ import { default as eventProcessor } from './plugins/event_processor'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; import { createHttpPollingDatafileManager } from './plugins/datafile_manager/browser_http_polling_datafile_manager'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; -import BrowserOptimizely from './optimizely/index.browser'; +import Optimizely from './optimizely'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -128,7 +128,7 @@ const createInstance = function(config: Config): Client | null { odpManager: new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), }; - const optimizely = new BrowserOptimizely(optimizelyOptions); + const optimizely = new Optimizely(optimizelyOptions); try { if (typeof window.addEventListener === 'function') { diff --git a/packages/optimizely-sdk/lib/index.node.ts b/packages/optimizely-sdk/lib/index.node.ts index 56ba69c71..20b977f94 100644 --- a/packages/optimizely-sdk/lib/index.node.ts +++ b/packages/optimizely-sdk/lib/index.node.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2017, 2019-2022 Optimizely, Inc. and contributors * + * Copyright 2016-2017, 2019-2023 Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -13,14 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, - setLogHandler, - setLogLevel -} from './modules/logging'; +import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; import Optimizely from './optimizely'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; @@ -32,6 +25,7 @@ import { createNotificationCenter } from './core/notification_center'; import { createEventProcessor } from './plugins/event_processor'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { createHttpPollingDatafileManager } from './plugins/datafile_manager/http_polling_datafile_manager'; +import { NodeOdpManager } from './plugins/odp_manager/index.node'; const logger = getLogger(); setLogLevel(LogLevel.ERROR); @@ -69,7 +63,7 @@ const createInstance = function(config: Config): Client | null { configValidator.validate(config); isValidInstance = true; // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex: any) { + } catch (ex) { if (hasLogger) { logger.error(ex); } else { @@ -100,9 +94,9 @@ const createInstance = function(config: Config): Client | null { dispatcher: config.eventDispatcher || defaultEventDispatcher, flushInterval: eventFlushInterval, batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, + maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, notificationCenter, - } + }; const eventProcessor = createEventProcessor(eventProcessorConfig); @@ -112,14 +106,17 @@ const createInstance = function(config: Config): Client | null { eventProcessor, logger, errorHandler, - datafileManager: config.sdkKey ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, + datafileManager: config.sdkKey + ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) + : undefined, notificationCenter, isValidInstance: isValidInstance, + odpManager: new NodeOdpManager({ logger, odpOptions: config.odpOptions }), }; return new Optimizely(optimizelyOptions); // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { + } catch (e) { logger.error(e); return null; } @@ -150,4 +147,4 @@ export default { OptimizelyDecideOption, }; -export * from './export_types' +export * from './export_types'; diff --git a/packages/optimizely-sdk/lib/optimizely/index.browser.ts b/packages/optimizely-sdk/lib/optimizely/index.browser.ts deleted file mode 100644 index 092950845..000000000 --- a/packages/optimizely-sdk/lib/optimizely/index.browser.ts +++ /dev/null @@ -1,62 +0,0 @@ -/**************************************************************************** - * Copyright 2023, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * https://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ - -import OptimizelyUserContext from '../optimizely_user_context'; -import Optimizely from '.'; -import { OptimizelyOptions, UserAttributes } from '../shared_types'; -import { BrowserOdpManager } from '../plugins/odp_manager/index.browser'; - -export default class BrowserOptimizely extends Optimizely { - constructor(config: OptimizelyOptions) { - super(config); - } - - /** - * @override - * Creates a context of the user for which decision APIs will be called. - * - * A user context will be created successfully even when the SDK is not fully configured yet, however, - * for just the browser variant ODP Manager is expected to have been instantiated already in order to - * access the stored vuid. - * - * @param {string} userId (Optional) The user ID to be used for bucketing. Defaults to VUID. - * @param {UserAttributes} attributes (Optional) Arbitrary attributes map. - * @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or - * null if provided inputs are invalid - */ - createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { - const userIdentifier = userId || this.getVuid(); - - if (!userIdentifier) { - return null; - } - - return super.createUserContext(userIdentifier, attributes); - } - - /** - * @returns {string|undefined} Currently provisioned VUID from local ODP Manager or undefined if - * ODP Manager has not been instantiated yet for any reason. - */ - getVuid(): string | undefined { - if (!this.odpManager) { - this.logger?.error('Unable to get VUID - ODP Manager is not instantiated yet.'); - return undefined; - } - - return (this.odpManager as BrowserOdpManager).vuid; - } -} diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 30fecc0f1..7b1f30dc3 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -4605,7 +4605,7 @@ describe('lib/optimizely', function() { }); it('should call the error handler for invalid user ID and return null', function() { - assert.isNull(optlyInstance.createUserContext(null)); + assert.isNull(optlyInstance.createUserContext(1)); sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); @@ -10091,7 +10091,6 @@ describe('lib/optimizely', function() { describe('odp', () => { var optlyInstanceWithOdp; var bucketStub; - var fakeDecisionResponse; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); var eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); var createdLogger = logger.createLogger({ diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 776d13a89..d31139374 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -63,7 +63,6 @@ import { NOTIFICATION_TYPES, NODE_CLIENT_ENGINE, NODE_CLIENT_VERSION, - ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE, } from '../utils/enums'; @@ -168,27 +167,42 @@ export default class Optimizely { const eventProcessorStartedPromise = this.eventProcessor.start(); - this.readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then( - promiseResults => { - if (config.odpManager != null) { - this.odpManager = config.odpManager; - this.odpManager.eventManager?.start(); - this.updateOdpSettings(); - const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; - if (sdkKey != null) { - NotificationRegistry.getNotificationCenter( - sdkKey, - this.logger - )?.addNotificationListener(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, () => this.updateOdpSettings()); - } else { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE); - } + // TODO: Look into making VUID Initialization a dependent promise for Browser Contexts + + // let dependentPromises: Promise[] = [projectConfigManagerReadyPromise]; + + // if (isBrowserContext()) { + // const odpManagerVuidInitializedPromise = (config.odpManager as BrowserOdpManager).initPromise; + // if (odpManagerVuidInitializedPromise) { + // dependentPromises.push(odpManagerVuidInitializedPromise); + // } + // } + + // this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { + + this.readyPromise = Promise.all([ + projectConfigManagerReadyPromise, + eventProcessorStartedPromise, + // config.odpManager.initPromise, + ]).then(promiseResults => { + if (config.odpManager != null) { + this.odpManager = config.odpManager; + this.odpManager.eventManager?.start(); + this.updateOdpSettings(); + const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; + if (sdkKey != null) { + NotificationRegistry.getNotificationCenter( + sdkKey, + this.logger + )?.addNotificationListener(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, () => this.updateOdpSettings()); + } else { + this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE); } - - // Only return status from project config promise because event processor promise does not return any status. - return promiseResults[0]; } - ); + + // Only return status from project config promise because event processor promise does not return any status. + return promiseResults[0]; + }); this.readyTimeouts = {}; this.nextReadyTimeoutId = 0; @@ -1428,19 +1442,27 @@ export default class Optimizely { * A user context will be created successfully even when the SDK is not fully configured yet, so no * this.isValidInstance() check is performed here. * - * @param {string} userId The user ID to be used for bucketing. - * @param {UserAttributes} attributes Optional user attributes. + * @param {string} userId (Optional) The user ID to be used for bucketing. + * @param {UserAttributes} attributes (Optional) user attributes. * @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or * null if provided inputs are invalid */ - createUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null { - if (!this.validateInputs({ user_id: userId }, attributes)) { + createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { + let userIdentifier; + + if (this.odpManager?.isVuidEnabled() && !userId) { + userIdentifier = userId || this.getVuid(); + } else { + userIdentifier = userId; + } + + if (!userIdentifier || !this.validateInputs({ user_id: userIdentifier }, attributes)) { return null; } return new OptimizelyUserContext({ optimizely: this, - userId, + userId: userIdentifier, attributes, shouldIdentifyUser: true, }); @@ -1662,23 +1684,17 @@ export default class Optimizely { /** * Sends an action as an ODP Event with optional custom parameters including type, identifiers, and data * Note: Since this depends on this.odpManager, it must await Optimizely client's onReady() promise resolution. - * @param {Object} odpEvent - * @param {ODP_EVENT_ACTION} odpEvent.action Subcategory of the event type (i.e. "client_initialized", or "identified") - * @param {string} odpEvent.type (Optional) Type of event (Defaults to "fullstack") - * @param {Map} odpEvent.identifiers (Optional) Key-value map of user identifiers - * @param {Map} odpEvent.data (Optional) Event data in a key-value map. + * @param {string} action Subcategory of the event type (i.e. "client_initialized", "identified", or a custom action) + * @param {string} type (Optional) Type of event (Defaults to "fullstack") + * @param {Map} identifiers (Optional) Key-value map of user identifiers + * @param {Map} data (Optional) Event data in a key-value map. */ - public async sendOdpEvent({ - type, - action, - identifiers, - data, - }: { - type?: string; - action: ODP_EVENT_ACTION; - identifiers?: Map; - data?: Map; - }): Promise { + public sendOdpEvent( + action: string, + type?: string, + identifiers?: Map, + data?: Map + ): void { if (!this.odpManager) { this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING); return; @@ -1688,7 +1704,7 @@ export default class Optimizely { try { const odpEvent = new OdpEvent(odpEventType, action, identifiers, data); - await this.odpManager.sendEvent(odpEvent); + this.odpManager.sendEvent(odpEvent); } catch (e) { this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED, e); } @@ -1698,9 +1714,9 @@ export default class Optimizely { * Identifies user with ODP server in a fire-and-forget manner. * @param {string} userId */ - public async identifyUser(userId: string): Promise { + public identifyUser(userId: string): void { if (this.odpManager && this.odpManager.enabled) { - await this.odpManager.identifyUser(userId); + this.odpManager.identifyUser(userId); } } @@ -1721,4 +1737,22 @@ export default class Optimizely { return await this.odpManager.fetchQualifiedSegments(userId, options); } + + /** + * @returns {string|undefined} Currently provisioned VUID from local ODP Manager or undefined if + * ODP Manager has not been instantiated yet for any reason. + */ + getVuid(): string | undefined { + if (!this.odpManager) { + this.logger?.error('Unable to get VUID - ODP Manager is not instantiated yet.'); + return undefined; + } + + if (!this.odpManager.isVuidEnabled()) { + this.logger.log(LOG_LEVEL.WARNING, 'getVuid() unavailable for this platform', MODULE_NAME); + return undefined; + } + + return this.odpManager.getVuid(); + } } diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts index 09936b40b..73ed3f64d 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts @@ -32,7 +32,24 @@ interface OptimizelyUserContextConfig { shouldIdentifyUser?: boolean; } -export default class OptimizelyUserContext { +export interface IOptimizelyUserContext { + qualifiedSegments: string[]; + getUserId(): string; + getAttributes(): UserAttributes; + setAttribute(key: string, value: unknown): void; + decide(key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision; + decideForKeys(keys: string[], options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; + decideAll(options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; + trackEvent(eventName: string, eventTags?: EventTags): void; + setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean; + getForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null; + removeForcedDecision(context: OptimizelyDecisionContext): boolean; + removeAllForcedDecisions(): boolean; + fetchQualifiedSegments(options?: OptimizelySegmentOption[]): Promise; + isQualifiedFor(segment: string): boolean; +} + +export default class OptimizelyUserContext implements IOptimizelyUserContext { private optimizely: Optimizely; private userId: string; private attributes: UserAttributes; diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts new file mode 100644 index 000000000..ab561c664 --- /dev/null +++ b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts @@ -0,0 +1,38 @@ +import { OdpEvent } from "../../../core/odp/odp_event"; +import { OdpEventApiManager } from "../../../core/odp/odp_event_api_manager"; +import { LogHandler, LogLevel } from '../../../modules/logging'; +import { ODP_EVENT_BROWSER_ENDPOINT } from '../../../utils/enums'; + +const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; + +export class BrowserOdpEventApiManager extends OdpEventApiManager { + protected shouldSendEvents(events: OdpEvent[]): boolean { + if (events.length <= 1) { + return true; + } + this.getLogger().log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (browser only supports batch size 1)`); + return false; + } + + protected generateRequestData(apiHost: string, apiKey: string, events: OdpEvent[]): { method: string; endpoint: string; headers: { [key: string]: string; }; data: string; } { + const method = 'GET'; + const event = events[0]; + const url = new URL(ODP_EVENT_BROWSER_ENDPOINT); + event.identifiers.forEach((v, k) =>{ + url.searchParams.append(k, v); + }); + event.data.forEach((v, k) =>{ + url.searchParams.append(k, v as string); + }); + url.searchParams.append('tracker_id', apiKey); + url.searchParams.append('event_type', event.type); + url.searchParams.append('vdl_action', event.action); + const endpoint = url.toString(); + return { + method, + endpoint, + headers: {}, + data: "", + }; + } +} \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts new file mode 100644 index 000000000..06dc79e8d --- /dev/null +++ b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts @@ -0,0 +1,28 @@ +import { OdpEvent } from "../../../core/odp/odp_event"; +import { OdpEventApiManager } from "../../../core/odp/odp_event_api_manager"; + +export class NodeOdpEventApiManager extends OdpEventApiManager { + protected shouldSendEvents(events: OdpEvent[]): boolean { + return true; + } + + protected generateRequestData(apiHost: string, apiKey: string, events: OdpEvent[]): { method: string; endpoint: string; headers: { [key: string]: string; }; data: string; } { + return { + method: 'POST', + endpoint: `${apiHost}/v3/events`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey, + }, + data: JSON.stringify(events, this.replacer), + } + } + + private replacer(_: unknown, value: unknown) { + if (value instanceof Map) { + return Object.fromEntries(value); + } else { + return value; + } + } +} \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts new file mode 100644 index 000000000..9ace87c16 --- /dev/null +++ b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts @@ -0,0 +1,27 @@ +import { OdpEventManager } from "../../../../lib/core/odp/odp_event_manager"; +import { LogLevel } from '../../../modules/logging'; + +const DEFAULT_BROWSER_QUEUE_SIZE = 100; + +export class BrowserOdpEventManager extends OdpEventManager { + protected initParams(batchSize: number | undefined, queueSize: number | undefined, flushInterval: number | undefined): void { + this.queueSize = queueSize || DEFAULT_BROWSER_QUEUE_SIZE; + + // disable event batching for browser + this.batchSize = 1; + this.flushInterval = 0; + + if (typeof batchSize !== 'undefined' && batchSize !== 1) { + this.getLogger().log(LogLevel.WARNING, 'ODP event batch size must be 1 in the browser.'); + } + + if (typeof flushInterval !== 'undefined' && flushInterval !== 0) { + this.getLogger().log(LogLevel.WARNING, 'ODP event flush interval must be 0 in the browser.'); + } + } + + protected discardEventsIfNeeded(): void { + // in Browser/client-side context, give debug message but leave events in queue + this.getLogger().log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); + } +} \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts new file mode 100644 index 000000000..83ddf0296 --- /dev/null +++ b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts @@ -0,0 +1,29 @@ + +import { OdpEvent } from "../../../../lib/core/odp/odp_event"; +import { OdpEventManager } from "../../../../lib/core/odp/odp_event_manager"; +import { LogLevel } from "../../../../lib/modules/logging"; + +const DEFAULT_BATCH_SIZE = 10; +const DEFAULT_FLUSH_INTERVAL_MSECS = 1000; +const DEFAULT_SERVER_QUEUE_SIZE = 10000; + +export class NodeOdpEventManager extends OdpEventManager { + protected initParams(batchSize: number | undefined, queueSize: number | undefined, flushInterval: number | undefined): void { + this.queueSize = queueSize || DEFAULT_SERVER_QUEUE_SIZE; + this.batchSize = batchSize || DEFAULT_BATCH_SIZE; + + if (flushInterval === 0) { + // disable event batching + this.batchSize = 1; + this.flushInterval = 0; + } else { + this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; + } + } + + protected discardEventsIfNeeded(): void { + // if Node/server-side context, empty queue items before ready state + this.getLogger().log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); + this.queue = new Array(); + } +} \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts index 028fe5430..7a2784a02 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts @@ -14,15 +14,16 @@ * limitations under the License. */ -import { - BROWSER_CLIENT_VERSION, - ERROR_MESSAGES, - JAVASCRIPT_CLIENT_ENGINE, - ODP_USER_KEY, - REQUEST_TIMEOUT_ODP_SEGMENTS_MS, - REQUEST_TIMEOUT_ODP_EVENTS_MS +import { + BROWSER_CLIENT_VERSION, + ERROR_MESSAGES, + JAVASCRIPT_CLIENT_ENGINE, + ODP_USER_KEY, + REQUEST_TIMEOUT_ODP_SEGMENTS_MS, + REQUEST_TIMEOUT_ODP_EVENTS_MS, + LOG_MESSAGES, } from '../../utils/enums'; -import { getLogger, LoggerFacade, LogHandler, LogLevel } from '../../modules/logging'; +import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { BrowserRequestHandler } from './../../utils/http_request_handler/browser_request_handler'; @@ -35,6 +36,10 @@ import { VuidManager } from './../vuid_manager/index'; import { OdpManager } from '../../core/odp/odp_manager'; import { OdpEvent } from '../../core/odp/odp_event'; import { OdpOptions } from '../../shared_types'; +import { BrowserOdpEventApiManager } from '../odp/event_api_manager/index.browser'; +import { BrowserOdpEventManager } from '../odp/event_manager/index.browser'; +import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; interface BrowserOdpManagerConfig { logger?: LogHandler; @@ -45,10 +50,18 @@ interface BrowserOdpManagerConfig { export class BrowserOdpManager extends OdpManager { static cache = new BrowserAsyncStorageCache(); vuid?: string; - initPromise?: Promise constructor({ logger, odpOptions }: BrowserOdpManagerConfig) { - const browserLogger = logger || getLogger('BrowserOdpManager'); + super(); + + this.logger = logger || getLogger(); + + if (odpOptions?.disabled) { + this.initPromise = Promise.resolve(); + this.enabled = false; + this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); + return; + } const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; const browserClientVersion = BROWSER_CLIENT_VERSION; @@ -59,38 +72,57 @@ export class BrowserOdpManager extends OdpManager { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; } else { customSegmentRequestHandler = new BrowserRequestHandler( - browserLogger, + this.logger, odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS ); } + // Set up Segment Manager (Audience Segments GraphQL API Interface) + if (odpOptions?.segmentManager) { + this.segmentManager = odpOptions.segmentManager; + this.segmentManager.updateSettings(this.odpConfig); + } else { + this.segmentManager = new OdpSegmentManager( + this.odpConfig, + odpOptions?.segmentsCache || + new BrowserLRUCache({ + maxSize: odpOptions?.segmentsCacheSize, + timeout: odpOptions?.segmentsCacheTimeout, + }), + new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) + ); + } + let customEventRequestHandler; if (odpOptions?.eventRequestHandler) { customEventRequestHandler = odpOptions.eventRequestHandler; } else { customEventRequestHandler = new BrowserRequestHandler( - browserLogger, + this.logger, odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS ); } - super({ - segmentLRUCache: - odpOptions?.segmentsCache || - new BrowserLRUCache({ - maxSize: odpOptions?.segmentsCacheSize, - timeout: odpOptions?.segmentsCacheTimeout, - }), - segmentRequestHandler: customSegmentRequestHandler, - eventRequestHandler: customEventRequestHandler, - logger: browserLogger, - clientEngine: browserClientEngine, - clientVersion: browserClientVersion, - odpOptions, - }); + // Set up Events Manager (Events REST API Interface) + if (odpOptions?.eventManager) { + this.eventManager = odpOptions.eventManager; + this.eventManager.updateSettings(this.odpConfig); + } else { + this.eventManager = new BrowserOdpEventManager({ + odpConfig: this.odpConfig, + apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, this.logger), + logger: this.logger, + clientEngine: browserClientEngine, + clientVersion: browserClientVersion, + flushInterval: odpOptions?.eventFlushInterval, + batchSize: odpOptions?.eventBatchSize, + queueSize: odpOptions?.eventQueueSize, + }); + } + + this.eventManager!.start(); - this.logger = browserLogger; this.initPromise = this.initializeVuid(BrowserOdpManager.cache).catch(e => { this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, e); }); @@ -124,8 +156,7 @@ export class BrowserOdpManager extends OdpManager { * - Additionally, also passes VUID to help identify client-side users * @param fsUserId Unique identifier of a target user. */ - public async identifyUser(fsUserId?: string, vuid?: string): Promise { - await this.initPromise; + public identifyUser(fsUserId?: string, vuid?: string): void { if (fsUserId && VuidManager.isVuid(fsUserId)) { super.identifyUser(undefined, fsUserId); return; @@ -146,8 +177,7 @@ export class BrowserOdpManager extends OdpManager { * - Identifiers must contain at least one key-value pair * @param {OdpEvent} odpEvent > ODP Event to send to event manager */ - public async sendEvent({ type, action, identifiers, data }: OdpEvent): Promise { - await this.initPromise; + public sendEvent({ type, action, identifiers, data }: OdpEvent): void { const identifiersWithVuid = new Map(identifiers); if (!identifiers.has(ODP_USER_KEY.VUID)) { @@ -160,4 +190,12 @@ export class BrowserOdpManager extends OdpManager { super.sendEvent({ type, action, identifiers: identifiersWithVuid, data }); } + + public isVuidEnabled(): boolean { + return true; + } + + public getVuid(): string | undefined { + return this.vuid; + } } diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts index 9b6e1a657..6f5324820 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts @@ -18,13 +18,23 @@ import { NodeRequestHandler } from '../../utils/http_request_handler/node_reques import { ServerLRUCache } from './../../utils/lru_cache/server_lru_cache'; +import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +import { + LOG_MESSAGES, + NODE_CLIENT_ENGINE, + NODE_CLIENT_VERSION, + REQUEST_TIMEOUT_ODP_EVENTS_MS, + REQUEST_TIMEOUT_ODP_SEGMENTS_MS, +} from '../../utils/enums'; + import { OdpManager } from '../../core/odp/odp_manager'; -import { getLogger, LogHandler } from '../../modules/logging'; -import { NODE_CLIENT_ENGINE, NODE_CLIENT_VERSION } from '../../utils/enums'; import { OdpOptions } from '../../../lib/shared_types'; +import { NodeOdpEventApiManager } from '../odp/event_api_manager/index.node'; +import { NodeOdpEventManager } from '../odp/event_manager/index.node'; +import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; interface NodeOdpManagerConfig { - disable: boolean; logger?: LogHandler; odpOptions?: OdpOptions; } @@ -35,25 +45,83 @@ interface NodeOdpManagerConfig { */ export class NodeOdpManager extends OdpManager { constructor({ logger, odpOptions }: NodeOdpManagerConfig) { - const nodeLogger = logger || getLogger(); + super(); + + this.logger = logger || getLogger(); + + if (odpOptions?.disabled) { + this.initPromise = Promise.resolve(); + this.enabled = false; + this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); + return; + } - const nodeRequestHandler = new NodeRequestHandler(nodeLogger); const nodeClientEngine = NODE_CLIENT_ENGINE; const nodeClientVersion = NODE_CLIENT_VERSION; - super({ - segmentLRUCache: + let customSegmentRequestHandler; + + if (odpOptions?.segmentsRequestHandler) { + customSegmentRequestHandler = odpOptions.segmentsRequestHandler; + } else { + customSegmentRequestHandler = new NodeRequestHandler( + this.logger, + odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS + ); + } + + // Set up Segment Manager (Audience Segments GraphQL API Interface) + if (odpOptions?.segmentManager) { + this.segmentManager = odpOptions.segmentManager; + this.segmentManager.updateSettings(this.odpConfig); + } else { + this.segmentManager = new OdpSegmentManager( + this.odpConfig, odpOptions?.segmentsCache || - new ServerLRUCache({ - maxSize: odpOptions?.segmentsCacheSize, - timeout: odpOptions?.segmentsCacheTimeout, - }), - segmentRequestHandler: nodeRequestHandler, - eventRequestHandler: nodeRequestHandler, - logger: nodeLogger, - clientEngine: nodeClientEngine, - clientVersion: nodeClientVersion, - odpOptions, - }); + new ServerLRUCache({ + maxSize: odpOptions?.segmentsCacheSize, + timeout: odpOptions?.segmentsCacheTimeout, + }), + new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) + ); + } + + let customEventRequestHandler; + + if (odpOptions?.eventRequestHandler) { + customEventRequestHandler = odpOptions.eventRequestHandler; + } else { + customEventRequestHandler = new NodeRequestHandler( + this.logger, + odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS + ); + } + + // Set up Events Manager (Events REST API Interface) + if (odpOptions?.eventManager) { + this.eventManager = odpOptions.eventManager; + this.eventManager.updateSettings(this.odpConfig); + } else { + this.eventManager = new NodeOdpEventManager({ + odpConfig: this.odpConfig, + apiManager: new NodeOdpEventApiManager(customEventRequestHandler, this.logger), + logger: this.logger, + clientEngine: nodeClientEngine, + clientVersion: nodeClientVersion, + flushInterval: odpOptions?.eventFlushInterval, + batchSize: odpOptions?.eventBatchSize, + queueSize: odpOptions?.eventQueueSize, + }); + } + + this.eventManager!.start(); + } + + public isVuidEnabled(): boolean { + return false; + } + + public getVuid(): string | undefined { + return undefined; } } diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index 4680bb12d..f0915931a 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -13,17 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * This file contains the shared type definitions collected from the SDK. + * These shared type definitions include ones that will be referenced by external consumers via export_types.ts. + */ + import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from '../lib/modules/logging'; import { EventProcessor } from '../lib/modules/event_processor'; import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; -import { NOTIFICATION_TYPES, ODP_EVENT_ACTION } from './utils/enums'; +import { NOTIFICATION_TYPES } from './utils/enums'; import { OdpManager } from './core/odp/odp_manager'; import { OdpSegmentManager } from './core/odp/odp_segment_manager'; import { LRUCache } from './utils/lru_cache'; import { OdpEventManager } from './core/odp/odp_event_manager'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; +import { OptimizelySegmentOption } from './core/odp/optimizely_segment_option'; +import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; export interface BucketerParams { experimentId: string; @@ -305,15 +313,11 @@ export interface OptimizelyVariable { value: string; } -export interface BrowserClient extends Client { - getVuid(): string; +export interface Client { // TODO: In the future, will add a function to allow overriding the VUID. + getVuid(): string | undefined; createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null; -} - -export interface Client { notificationCenter: NotificationCenter; - createUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null; activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null; track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void; getVariation(experimentKey: string, userId: string, attributes?: UserAttributes): string | null; @@ -355,12 +359,7 @@ export interface Client { getOptimizelyConfig(): OptimizelyConfig | null; onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }>; close(): Promise<{ success: boolean; reason?: string }>; - sendOdpEvent(payload: { - type?: string; - action: ODP_EVENT_ACTION; - identifiers?: Map; - data?: Map; - }): void; + sendOdpEvent(action: string, type?: string, identifiers?: Map, data?: Map): void; } export interface ActivateListenerPayload extends ListenerPayload { @@ -486,21 +485,7 @@ export interface OptimizelyConfig { getDatafile(): string; } -export interface OptimizelyUserContext { - getUserId(): string; - getAttributes(): UserAttributes; - setAttribute(key: string, value: unknown): void; - decide(key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision; - decideForKeys(keys: string[], options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; - decideAll(options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; - trackEvent(eventName: string, eventTags?: EventTags): void; - setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean; - getForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null; - removeForcedDecision(context: OptimizelyDecisionContext): boolean; - removeAllForcedDecisions(): boolean; - fetchQualifiedSegments(): Promise; - isQualifiedFor(segment: string): boolean; -} +export { OptimizelyUserContext }; export interface OptimizelyDecision { variationKey: string | null; @@ -547,3 +532,5 @@ export interface OptimizelyDecisionContext { export interface OptimizelyForcedDecision { variationKey: string; } + +export { OptimizelySegmentOption }; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts index 578daa479..eaf72fafa 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import LRUCache, { ISegmentsCacheConfig, LRUCacheConfig } from './lru_cache'; +import LRUCache, { ISegmentsCacheConfig } from './lru_cache'; export interface ServerLRUCacheConfig { maxSize?: number; diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json index 6383bb03b..452984338 100644 --- a/packages/optimizely-sdk/package-lock.json +++ b/packages/optimizely-sdk/package-lock.json @@ -1,126 +1,40 @@ { "name": "@optimizely/optimizely-sdk", "version": "4.9.3", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "@optimizely/optimizely-sdk", - "version": "4.9.3", - "license": "Apache-2.0", - "dependencies": { - "@optimizely/js-sdk-datafile-manager": "^0.9.5", - "decompress-response": "^4.2.1", - "json-schema": "^0.4.0", - "murmurhash": "^2.0.1", - "uuid": "^8.3.2" - }, - "devDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "^5.9.10", - "@rollup/plugin-commonjs": "^11.0.2", - "@rollup/plugin-node-resolve": "^7.1.1", - "@types/chai": "^4.2.11", - "@types/jest": "^23.3.14", - "@types/mocha": "^5.2.7", - "@types/nise": "^1.4.0", - "@types/node": "^18.7.18", - "@types/uuid": "^3.4.4", - "@typescript-eslint/eslint-plugin": "^5.33.0", - "@typescript-eslint/parser": "^5.33.0", - "bluebird": "^3.4.6", - "chai": "^4.2.0", - "coveralls": "^3.0.2", - "eslint": "^8.21.0", - "eslint-config-prettier": "^6.10.0", - "eslint-plugin-prettier": "^3.1.2", - "jest": "^28.1.3", - "jest-environment-jsdom": "^29.0.0", - "jest-localstorage-mock": "^2.4.22", - "json-loader": "^0.5.4", - "karma": "^6.4.0", - "karma-browserstack-launcher": "^1.5.1", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.1.1", - "karma-mocha": "^1.3.0", - "karma-webpack": "^5.0.0", - "lodash": "^4.17.11", - "mocha": "^5.2.0", - "mocha-lcov-reporter": "^1.3.0", - "nise": "^1.4.10", - "nock": "11.9.1", - "nyc": "^15.0.1", - "prettier": "^1.19.1", - "promise-polyfill": "8.1.0", - "rollup": "2.2.0", - "rollup-plugin-terser": "^5.3.0", - "rollup-plugin-typescript2": "^0.27.1", - "sinon": "^2.3.1", - "ts-jest": "^28.0.8", - "ts-loader": "^9.3.1", - "ts-mockito": "^2.6.1", - "ts-node": "^8.10.2", - "tslib": "^2.4.0", - "typescript": "^4.7.4", - "webpack": "^5.74.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@babel/runtime": "^7.0.0", - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "5.9.4" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - }, - "@react-native-community/netinfo": { - "optional": true - } - } - }, - "node_modules/@ampproject/remapping": { + "dependencies": { + "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" } }, - "node_modules/@babel/code-frame": { + "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { + "@babel/compat-data": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } + "dev": true }, - "node_modules/@babel/core": { + "@babel/core": { "version": "7.18.13", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", "@babel/generator": "^7.18.13", @@ -137,274 +51,111 @@ "json5": "^2.2.1", "semver": "^6.3.0" }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "devOptional": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "node_modules/@babel/generator": { + "@babel/generator": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/types": "^7.19.0", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "devOptional": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/generator/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "devOptional": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + } } }, - "node_modules/@babel/helper-compilation-targets": { + "@babel/helper-compilation-targets": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/compat-data": "^7.19.1", "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", "semver": "^6.3.0" }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "node_modules/@babel/helper-environment-visitor": { + "@babel/helper-environment-visitor": { "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } + "dev": true }, - "node_modules/@babel/helper-function-name": { + "@babel/helper-function-name": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/template": "^7.18.10", "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { + "@babel/helper-hoist-variables": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-imports": { + "@babel/helper-module-imports": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-transforms": { + "@babel/helper-module-transforms": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-module-imports": "^7.18.6", "@babel/helper-simple-access": "^7.18.6", @@ -413,32379 +164,6388 @@ "@babel/template": "^7.18.10", "@babel/traverse": "^7.19.0", "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-plugin-utils": { + "@babel/helper-plugin-utils": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } + "dev": true }, - "node_modules/@babel/helper-simple-access": { + "@babel/helper-simple-access": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", - "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { + "@babel/helper-split-export-declaration": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-string-parser": { + "@babel/helper-string-parser": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } + "dev": true }, - "node_modules/@babel/helper-validator-identifier": { + "@babel/helper-validator-identifier": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } + "dev": true }, - "node_modules/@babel/helper-validator-option": { + "@babel/helper-validator-option": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } + "dev": true }, - "node_modules/@babel/helpers": { + "@babel/helpers": { "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/template": "^7.18.6", "@babel/traverse": "^7.18.9", "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { + "@babel/highlight": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { + "@babel/parser": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "devOptional": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", - "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { + "@babel/plugin-syntax-class-properties": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { + "@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { + "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { + "@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { + "@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { + "@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { + "@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "devOptional": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { + "@babel/plugin-syntax-typescript": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "devOptional": true, - "peer": true, - "dependencies": { + "dev": true, + "requires": { "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", - "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", + "@babel/traverse": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", + "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.19.1", + "@babel/types": "^7.19.0", + "debug": "^4.1.0", "globals": "^11.1.0" }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz", - "integrity": "sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@babel/types": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + } } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz", - "integrity": "sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@jest/environment": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", + "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.0.1", + "@jest/types": "^29.0.1", + "@types/node": "*", + "jest-mock": "^29.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "dependencies": { + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", + "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", - "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz", - "integrity": "sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz", - "integrity": "sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" + "@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "requires": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" + "@jest/fake-timers": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", + "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", + "dev": true, + "requires": { + "@jest/types": "^29.0.1", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.0.1", + "jest-mock": "^29.0.1", + "jest-util": "^29.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", + "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-message-util": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", + "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.0.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-util": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", + "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "pretty-format": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", + "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" + "@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + } } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz", - "integrity": "sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" + "@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@babel/preset-env": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.1.tgz", - "integrity": "sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-compilation-targets": "^7.19.1", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.9", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.18.13", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" + "@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" } }, - "node_modules/@babel/preset-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.18.6.tgz", - "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-flow-strip-types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "devOptional": true, - "peer": true, "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@babel/register/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "devOptional": true, - "peer": true, "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "devOptional": true, - "peer": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@babel/register/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/register/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "devOptional": true, - "peer": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/register/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true }, - "node_modules/@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "peer": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true }, - "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "devOptional": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "devOptional": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true, - "engines": { - "node": ">=4" - } + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true }, - "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "devOptional": true, - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" + "@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/types/node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "devOptional": true, - "engines": { - "node": ">=4" + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "engines": { - "node": ">=0.1.90" + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "@optimizely/js-sdk-datafile-manager": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-datafile-manager/-/js-sdk-datafile-manager-0.9.5.tgz", + "integrity": "sha512-O4ujr1nBBAQBtx8YoKNpzzaEZgsE+aU4dxubT17ePqv/YVUWE+JOY21tSRrqZy/BlbbyzL+ElT8hrGB5ZzVoIQ==", + "requires": { + "@optimizely/js-sdk-logging": "^0.3.1", + "@optimizely/js-sdk-utils": "^0.4.0", + "decompress-response": "^4.2.1" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "devOptional": true, - "peer": true - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" + "@optimizely/js-sdk-logging": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", + "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", + "requires": { + "@optimizely/js-sdk-utils": "^0.4.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@optimizely/js-sdk-utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", + "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", + "requires": { + "uuid": "^3.3.2" }, - "engines": { - "node": ">=10.10.0" + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } } }, - "node_modules/@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "@react-native-async-storage/async-storage": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", + "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "requires": { + "merge-options": "^3.0.4" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "@react-native-community/netinfo": { + "version": "5.9.10", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", + "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", "dev": true }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "@rollup/plugin-commonjs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", + "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@rollup/pluginutils": "^3.0.8", + "commondir": "^1.0.1", + "estree-walker": "^1.0.1", + "glob": "^7.1.2", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "@rollup/plugin-node-resolve": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", + "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", "dev": true, - "engines": { - "node": ">=6" + "requires": { + "@rollup/pluginutils": "^3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.14.2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "@sinclair/typebox": { + "version": "0.24.34", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", + "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "requires": { + "type-detect": "4.0.8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "@babel/types": "^7.0.0" } }, - "node_modules/@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@types/babel__traverse": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", + "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "requires": { + "@babel/types": "^7.3.0" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "@types/chai": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", + "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "dev": true + }, + "@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "@types/eslint": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", + "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "@types/node": "*" } }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "23.3.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", + "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", + "dev": true + }, + "@types/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "dev": true, + "requires": { "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "@types/tough-cookie": "*", + "parse5": "^7.0.0" } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } + "@types/nise": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", + "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", + "dev": true }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", "dev": true }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", - "integrity": "sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "requires": { + "@types/node": "*" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true }, - "node_modules/@jest/create-cache-key-function/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true }, - "node_modules/@jest/create-cache-key-function/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "@types/uuid": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", + "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", + "dev": true }, - "node_modules/@jest/create-cache-key-function/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" } }, - "node_modules/@jest/create-cache-key-function/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true }, - "node_modules/@jest/create-cache-key-function/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" + "@typescript-eslint/eslint-plugin": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", + "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/type-utils": "5.33.0", + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@typescript-eslint/parser": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", + "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "debug": "^4.3.4" } }, - "node_modules/@jest/environment": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", - "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", + "@typescript-eslint/scope-manager": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", + "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/node": "*", - "jest-mock": "^29.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0" } }, - "node_modules/@jest/environment/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "@typescript-eslint/type-utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", + "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "@typescript-eslint/types": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", + "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", + "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" } }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@typescript-eslint/utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", + "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" } }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "@typescript-eslint/visitor-keys": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", + "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "@typescript-eslint/types": "5.33.0", + "eslint-visitor-keys": "^3.3.0" } }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" } }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", "dev": true }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" } }, - "node_modules/@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", "dev": true, - "dependencies": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" } }, - "node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/@jest/fake-timers": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", - "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", "dev": true, - "dependencies": { - "@jest/types": "^29.0.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.0.1", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@xtuc/long": "4.2.2" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" } }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" } }, - "node_modules/@jest/fake-timers/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true }, - "node_modules/@jest/fake-timers/node_modules/jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true }, - "node_modules/@jest/fake-timers/node_modules/jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, - "dependencies": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, - "node_modules/@jest/fake-timers/node_modules/pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true }, - "node_modules/@jest/fake-timers/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", "dev": true, - "engines": { - "node": ">=10" + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } } }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "acorn-walk": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "es6-promisify": "^5.0.0" } }, - "node_modules/@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "node_modules/@jest/globals/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true }, - "node_modules/@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "requires": { + "type-fest": "^0.21.3" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true } } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "requires": { + "color-convert": "^1.9.0" } }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "sprintf-js": "~1.0.2" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "dev": true }, - "node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true }, - "node_modules/@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "safer-buffer": "~2.1.0" } }, - "node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, - "node_modules/@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true }, - "node_modules/@jest/transform": { + "babel-jest": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", + "requires": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" + "slash": "^3.0.0" }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" } }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" } }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" } }, - "node_modules/@jest/types": { + "babel-preset-jest": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "tweetnacl": "^0.14.3" } }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "devOptional": true, "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "devOptional": true, - "engines": { - "node": ">=6.0.0" + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + } } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "requires": { + "fill-range": "^7.0.1" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "devOptional": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "requires": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", "dev": true, - "engines": { - "node": ">= 8" + "requires": { + "https-proxy-agent": "^2.2.1" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "browserstack-local": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", + "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "requires": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@optimizely/js-sdk-datafile-manager": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-datafile-manager/-/js-sdk-datafile-manager-0.9.5.tgz", - "integrity": "sha512-O4ujr1nBBAQBtx8YoKNpzzaEZgsE+aU4dxubT17ePqv/YVUWE+JOY21tSRrqZy/BlbbyzL+ElT8hrGB5ZzVoIQ==", "dependencies": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0", - "decompress-response": "^4.2.1" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } } } }, - "node_modules/@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "dependencies": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "node_modules/@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "dependencies": { - "uuid": "^3.3.2" - } - }, - "node_modules/@optimizely/js-sdk-utils/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/@react-native-async-storage/async-storage": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", - "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", - "devOptional": true, - "dependencies": { - "merge-options": "^3.0.4" - }, - "peerDependencies": { - "react-native": "^0.0.0-0 || 0.60 - 0.69 || 1000.0.0" + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" } }, - "node_modules/@react-native-community/cli": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz", - "integrity": "sha512-E36hU/if3quQCfJHGWVkpsCnwtByRCwORuAX0r6yr1ebKktpKeEO49zY9PAu/Z1gfyxCtgluXY0HfRxjKRFXTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-clean": "^8.0.4", - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-doctor": "^8.0.6", - "@react-native-community/cli-hermes": "^8.0.5", - "@react-native-community/cli-plugin-metro": "^8.0.4", - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "@react-native-community/cli-types": "^8.0.0", - "chalk": "^4.1.2", - "commander": "^2.19.0", - "execa": "^1.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "minimist": "^1.2.0", - "prompts": "^2.4.0", - "semver": "^6.3.0" - }, - "bin": { - "react-native": "build/bin.js" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react-native": "*" + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" } }, - "node_modules/@react-native-community/cli-clean": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz", - "integrity": "sha512-IwS1M1NHg6+qL8PThZYMSIMYbZ6Zbx+lIck9PLBskbosFo24M3lCOflOl++Bggjakp6mR+sRXxLMexid/GeOsQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "prompts": "^2.4.0" - } + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-clean/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + } } }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" } }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "caniuse-lite": { + "version": "1.0.30001407", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", + "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" } }, - "node_modules/@react-native-community/cli-clean/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "node_modules/@react-native-community/cli-clean/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true }, - "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-config": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-8.0.6.tgz", - "integrity": "sha512-mjVpVvdh8AviiO8xtqeX+BkjqE//NMDnISwsLWSJUfNCwTAPmdR8PGbhgP5O4hWHyJ3WkepTopl0ya7Tfi3ifw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "glob": "^7.1.3", - "joi": "^17.2.1" + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" } }, - "node_modules/@react-native-community/cli-config/node_modules/deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-8.0.0.tgz", - "integrity": "sha512-u2jq06GZwZ9sRERzd9FIgpW6yv4YOW4zz7Ym/B8eSzviLmy3yI/8mxJtvlGW+J8lBsfMcQoqJpqI6Rl1nZy9yQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "serve-static": "^1.13.1" - } + "ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true }, - "node_modules/@react-native-community/cli-doctor": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-8.0.6.tgz", - "integrity": "sha512-ZQqyT9mJMVeFEVIwj8rbDYGCA2xXjJfsQjWk2iTRZ1CFHfhPSUuUiG8r6mJmTinAP9t+wYcbbIYzNgdSUKnDMw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-platform-ios": "^8.0.6", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "envinfo": "^7.7.2", - "execa": "^1.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "prompts": "^2.4.0", - "semver": "^6.3.0", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^9.0.0", - "wcwidth": "^1.0.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-doctor/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, - "node_modules/@react-native-community/cli-doctor/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, - "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true }, - "node_modules/@react-native-community/cli-hermes": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-8.0.5.tgz", - "integrity": "sha512-Zm0wM6SfgYAEX1kfJ1QBvTayabvh79GzmjHyuSnEROVNPbl4PeCG4WFbwy489tGwOP9Qx9fMT5tRIFCD8bp6/g==", - "devOptional": true, - "peer": true, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, "dependencies": { - "@react-native-community/cli-platform-android": "^8.0.5", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, - "node_modules/@react-native-community/cli-hermes/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true }, - "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, - "node_modules/@react-native-community/cli-hermes/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-hermes/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" + "coveralls": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" } }, - "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, - "engines": { - "node": ">=8" + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-8.0.5.tgz", - "integrity": "sha512-z1YNE4T1lG5o9acoQR1GBvf7mq6Tzayqo/za5sHVSOJAC9SZOuVN/gg/nkBa9a8n5U7qOMFXfwhTMNqA474gXA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.3", - "jetifier": "^1.6.2", - "lodash": "^4.17.15", - "logkitty": "^0.7.1", - "slash": "^3.0.0" - } + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-android/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } + "date-format": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", + "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, - "node_modules/@react-native-community/cli-platform-android/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } + "decimal.js": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-android/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-android/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" } }, - "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-8.0.6.tgz", - "integrity": "sha512-CMR6mu/LVx6JVfQRDL9uULsMirJT633bODn+IrYmrwSz250pnhON16We8eLPzxOZHyDjm7JPuSgHG3a/BPiRuQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "glob": "^7.1.3", - "js-yaml": "^3.13.1", - "lodash": "^4.17.15", - "ora": "^5.4.1", - "plist": "^3.0.2" - } + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" }, - "engines": { - "node": ">=4" + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" } }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, - "node_modules/@react-native-community/cli-plugin-metro": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-8.0.4.tgz", - "integrity": "sha512-UWzY1eMcEr/6262R2+d0Is5M3L/7Y/xXSDIFMoc5Rv5Wucl3hJM/TxHXmByvHpuJf6fJAfqOskyt4bZCvbI+wQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "metro": "^0.70.1", - "metro-config": "^0.70.1", - "metro-core": "^0.70.1", - "metro-react-native-babel-transformer": "^0.70.1", - "metro-resolver": "^0.70.1", - "metro-runtime": "^0.70.1", - "readline": "^1.3.0" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } + "electron-to-chromium": { + "version": "1.4.256", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", + "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", + "dev": true }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true + "emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true }, - "node_modules/@react-native-community/cli-server-api": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-8.0.4.tgz", - "integrity": "sha512-Orr14njx1E70CVrUA8bFdl+mrnbuXUjf1Rhhm0RxUadFpvkHuOi5dh8Bryj2MKtf8eZrpEwZ7tuQPhJEULW16A==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-tools": "^8.0.4", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.0", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true + } } }, - "node_modules/@react-native-community/cli-server-api/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true }, - "node_modules/@react-native-community/cli-server-api/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-server-api/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true }, - "node_modules/@react-native-community/cli-server-api/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-8.0.4.tgz", - "integrity": "sha512-ePN9lGxh6LRFiotyddEkSmuqpQhnq2iw9oiXYr4EFWpIEy0yCigTuSTiDF68+c8M9B+7bTwkRpz/rMPC4ViO5Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "lodash": "^4.17.15", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^6.3.0", - "shell-quote": "^1.7.3" - } + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true }, - "node_modules/@react-native-community/cli-tools/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true }, - "node_modules/@react-native-community/cli-tools/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true }, - "node_modules/@react-native-community/cli-tools/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" } }, - "node_modules/@react-native-community/cli-tools/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true }, - "node_modules/@react-native-community/cli-tools/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true }, - "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, - "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-8.0.0.tgz", - "integrity": "sha512-1lZS1PEvMlFaN3Se1ksyoFWzMjk+YfKi490GgsqKJln9gvFm8tqVPdnXttI5Uf2DQf3BMse8Bk8dNH4oV6Ewow==", - "devOptional": true, - "peer": true, "dependencies": { - "joi": "^17.2.1" + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } } }, - "node_modules/@react-native-community/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.3", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/@react-native-community/cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" + "eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" } }, - "node_modules/@react-native-community/cli/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" + "eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" } }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" } }, - "node_modules/@react-native-community/cli/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } } }, - "node_modules/@react-native-community/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" + "espree": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, - "node_modules/@react-native-community/cli/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, - "node_modules/@react-native-community/cli/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@react-native-community/cli/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, - "node_modules/@react-native-community/cli/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true }, - "node_modules/@react-native-community/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true }, - "node_modules/@react-native-community/netinfo": { - "version": "5.9.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", - "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, - "peerDependencies": { - "react-native": ">=0.59" + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" } }, - "node_modules/@react-native/assets": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz", - "integrity": "sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native/normalize-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.0.0.tgz", - "integrity": "sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw==", - "devOptional": true, - "peer": true + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true }, - "node_modules/@react-native/polyfills": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/polyfills/-/polyfills-2.0.0.tgz", - "integrity": "sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==", - "devOptional": true, - "peer": true + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true }, - "node_modules/@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" } }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", "dev": true, - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" + "requires": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" } }, - "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, - "node_modules/@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", - "devOptional": true, - "peer": true + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "devOptional": true, - "peer": true + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, - "node_modules/@sinclair/typebox": { - "version": "0.24.34", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", - "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, - "dependencies": { - "type-detect": "4.0.8" + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" + "requires": { + "reusify": "^1.0.4" } }, - "node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "requires": { + "bser": "2.1.1" } }, - "node_modules/@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "requires": { + "flat-cache": "^3.0.4" } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, - "engines": { - "node": ">= 10" + "requires": { + "to-regex-range": "^5.0.1" + }, + "dependencies": { + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, - "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } } }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } } }, - "node_modules/@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, "dependencies": { - "@babel/types": "^7.3.0" + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, - "node_modules/@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, - "node_modules/@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", "dev": true }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } }, - "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true }, - "node_modules/@types/eslint": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", - "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "requires": { + "samsam": "1.x" } }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "devOptional": true, - "dependencies": { - "@types/node": "*" - } + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "devOptional": true + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "requires": { + "null-check": "^1.0.0" + } }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "devOptional": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "devOptional": true, - "dependencies": { - "@types/istanbul-lib-report": "*" + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, - "node_modules/@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "node_modules/@types/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } + "optional": true }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "node_modules/@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, - "node_modules/@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", - "devOptional": true + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, - "node_modules/@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, - "node_modules/@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", "dev": true, - "dependencies": { - "@types/node": "*" + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, - "node_modules/@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true }, - "node_modules/@types/uuid": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", - "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, - "node_modules/@types/yargs": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", - "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "requires": { + "assert-plus": "^1.0.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "devOptional": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", - "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/type-utils": "5.33.0", - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", - "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "requires": { + "is-glob": "^4.0.1" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", - "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, - "dependencies": { - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" + "requires": { + "type-fest": "^0.20.2" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, - "node_modules/@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true }, - "node_modules/@typescript-eslint/utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", - "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "requires": { + "function-bind": "^1.1.1" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + } } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" + "requires": { + "whatwg-encoding": "^2.0.0" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + } } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "devOptional": true, - "peer": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/absolute-path": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==", - "devOptional": true, - "peer": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "devOptional": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" } }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "requires": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, - "node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "binary-extensions": "^2.0.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "requires": { + "has": "^1.0.3" } }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "devOptional": true, - "peer": true + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "is-extglob": "^2.1.1" } }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "devOptional": true, - "peer": true, - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, - "engines": { - "node": ">=8" - } + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "devOptional": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } + "is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", + "dev": true }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "devOptional": true, - "peer": true + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true }, - "node_modules/archy": { + "is-typedarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "devOptional": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true }, - "node_modules/array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "devOptional": true, - "peer": true - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "devOptional": true, - "peer": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "devOptional": true, - "peer": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "devOptional": true, - "peer": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "devOptional": true, - "peer": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "devOptional": true, - "peer": true, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", "dev": true, - "dependencies": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", "supports-color": "^7.1.0" }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "devOptional": true, - "peer": true, "dependencies": { - "object.assign": "^4.1.0" + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, - "node_modules/babel-plugin-jest-hoist": { + "jest": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" + "requires": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "devOptional": true, - "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "requires": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } } }, - "node_modules/babel-plugin-syntax-trailing-function-commas": { - "version": "7.0.0-beta.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", - "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", - "devOptional": true, - "peer": true - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-fbjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", - "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-class-properties": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-member-expression-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-property-literals": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" } }, - "node_modules/babel-preset-jest": { + "jest-circus": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "devOptional": true, - "peer": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } }, - { - "type": "consulting", - "url": "https://feross.org/support" + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } - ], - "peer": true - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, - "peer": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "dependencies": { - "ms": "2.0.0" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "devOptional": true, "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" } }, - "node_modules/browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", + "jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "requires": { + "detect-newline": "^3.0.0" } }, - "node_modules/browserstack-local/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", "dev": true, - "dependencies": { - "debug": "4" + "requires": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/browserstack-local/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "jest-environment-jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", + "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" + "requires": { + "@jest/environment": "^29.0.1", + "@jest/fake-timers": "^29.0.1", + "@jest/types": "^29.0.1", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.0.1", + "jest-util": "^29.0.1", + "jsdom": "^20.0.0" }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "devOptional": true, "dependencies": { - "node-int64": "^0.4.0" + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", + "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-util": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", + "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "dependencies": { + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } }, - { - "type": "consulting", - "url": "https://feross.org/support" + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } } - ], - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "requires": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", "dev": true, - "engines": { - "node": ">= 0.8" + "requires": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" } }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "jest-localstorage-mock": { + "version": "2.4.22", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", + "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", + "dev": true }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/caching-transform/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "devOptional": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "callsites": "^2.0.0" + "jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "devOptional": true, - "peer": true, "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "jest-mock": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", + "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "devOptional": true, - "engines": { - "node": ">=10" + "requires": { + "@jest/types": "^29.0.3", + "@types/node": "*" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001407", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", - "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "dependencies": { + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", + "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } - ] + } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", "dev": true }, - "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "devOptional": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "devOptional": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", "dev": true }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "devOptional": true, - "peer": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "devOptional": true, - "peer": true, "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "devOptional": true, - "peer": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, - "dependencies": { - "color-name": "1.1.3" + "requires": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "devOptional": true, - "peer": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" + "requires": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "devOptional": true, - "peer": true - }, - "node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "devOptional": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "devOptional": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "devOptional": true, - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, "dependencies": { - "ms": "2.0.0" + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "devOptional": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, "dependencies": { - "ms": "2.0.0" + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "devOptional": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/convert-source-map/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js-compat": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.2.tgz", - "integrity": "sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4" + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "devOptional": true - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "devOptional": true, - "peer": true, "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/cosmiconfig/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "devOptional": true, - "peer": true, - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.3" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "devOptional": true, - "peer": true, "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - }, - "bin": { - "coveralls": "bin/coveralls.js" + "requires": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, - "engines": { - "node": ">= 8" + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", "dev": true, - "dependencies": { + "requires": { "abab": "^2.0.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "^7.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/date-format": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", - "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", - "devOptional": true, - "peer": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "devOptional": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true }, - "node_modules/decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10" - } + "json-schema": { + "version": "0.4.0", + "resolved": "https://nexus.es.ecg.tools/repository/npm-all/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, - "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "devOptional": true, - "peer": true, - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "devOptional": true, - "peer": true - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "devOptional": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "requires": { + "graceful-fs": "^4.1.6" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" } }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "karma": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", + "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", "dev": true, - "engines": { - "node": ">=0.3.1" + "requires": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } } }, - "node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "karma-browserstack-launcher": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", + "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + "requires": { + "browserstack": "~1.5.1", + "browserstack-local": "^1.3.7", + "q": "~1.5.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", + "dev": true }, - "node_modules/dir-glob/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "fs-access": "^1.0.0", + "which": "^1.2.1" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "karma-mocha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", + "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=", "dev": true, - "dependencies": { - "esutils": "^2.0.2" + "requires": { + "minimist": "1.2.0" }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } } }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "karma-webpack": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", + "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", "dev": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "webpack-merge": "^4.1.5" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "devOptional": true + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, - "node_modules/electron-to-chromium": { - "version": "1.4.256", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", - "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", - "devOptional": true + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true }, - "node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "requires": { + "p-locate": "^5.0.0" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", - "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", - "dev": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/entities": { + "lodash.flattendeep": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "devOptional": true, - "peer": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "devOptional": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "devOptional": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dev": true, - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "devOptional": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "devOptional": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", - "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.3", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true }, - "node_modules/eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "log4js": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", + "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", "dev": true, - "dependencies": { - "get-stdin": "^6.0.0" - }, - "bin": { - "eslint-config-prettier-check": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=3.14.1" + "requires": { + "date-format": "^4.0.13", + "debug": "^4.3.4", + "flatted": "^3.2.6", + "rfdc": "^1.3.0", + "streamroller": "^3.1.2" } }, - "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "devOptional": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "devOptional": true, - "peer": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "devOptional": true, - "peer": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "devOptional": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "devOptional": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fill-range/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "devOptional": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "devOptional": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "node_modules/flow-parser": { - "version": "0.121.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz", - "integrity": "sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/foreground-child/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/foreground-child/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", - "deprecated": "This package is unmaintained. Use @sinonjs/formatio instead", - "dev": true, - "dependencies": { - "samsam": "1.x" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "devOptional": true, - "peer": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", - "dev": true, - "dependencies": { - "null-check": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "devOptional": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "devOptional": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "devOptional": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "devOptional": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "devOptional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "devOptional": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "devOptional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "devOptional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "devOptional": true, - "peer": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hermes-engine": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz", - "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw==", - "devOptional": true, - "peer": true - }, - "node_modules/hermes-estree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.6.0.tgz", - "integrity": "sha512-2YTGzJCkhdmT6VuNprWjXnvTvw/3iPNw804oc7yknvQpNKo+vJGZmtvLLCghOZf0OwzKaNAzeIMp71zQbNl09w==", - "devOptional": true, - "peer": true - }, - "node_modules/hermes-parser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", - "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "hermes-estree": "0.6.0" - } - }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hermes-profile-transformer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "devOptional": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", - "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", - "devOptional": true, - "peer": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "devOptional": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "devOptional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", - "devOptional": true, - "peer": true - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "devOptional": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "devOptional": true, - "peer": true - }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "devOptional": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "devOptional": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "devOptional": true, - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", - "dev": true - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "devOptional": true, - "peer": true - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", - "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.0.1", - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1", - "jsdom": "^20.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-environment-jsdom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom/node_modules/jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-localstorage-mock": { - "version": "2.4.22", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", - "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", - "dev": true, - "engines": { - "node": ">=6.16.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", - "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", - "dev": true, - "dependencies": { - "@jest/types": "^29.0.3", - "@types/node": "*" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", - "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/jetifier": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.8.tgz", - "integrity": "sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw==", - "devOptional": true, - "peer": true, - "bin": { - "jetifier": "bin/jetify", - "jetifier-standalone": "bin/jetifier-standalone", - "jetify": "bin/jetify" - } - }, - "node_modules/joi": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", - "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "devOptional": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "devOptional": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "node_modules/jsc-android": { - "version": "250230.2.1", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250230.2.1.tgz", - "integrity": "sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q==", - "devOptional": true, - "peer": true - }, - "node_modules/jscodeshift": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.13.1.tgz", - "integrity": "sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, - "node_modules/jscodeshift/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jscodeshift/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jscodeshift/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jscodeshift/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/jscodeshift/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jscodeshift/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "devOptional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/jscodeshift/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jscodeshift/node_modules/temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "devOptional": true, - "peer": true, - "dependencies": { - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.8.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/jsdom/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsdom/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "devOptional": true, - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "devOptional": true, - "peer": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://nexus.es.ecg.tools/repository/npm-all/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "devOptional": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "node_modules/karma": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", - "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", - "dev": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", - "dev": true, - "dependencies": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" - }, - "peerDependencies": { - "karma": ">=0.9" - } - }, - "node_modules/karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true, - "peerDependencies": { - "chai": "*", - "karma": ">=0.10.9" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "dependencies": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "node_modules/karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", - "dev": true, - "dependencies": { - "minimist": "1.2.0" - } - }, - "node_modules/karma-mocha/node_modules/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", - "dev": true - }, - "node_modules/karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "webpack-merge": "^4.1.5" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/karma/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma/node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "devOptional": true, - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true, - "bin": { - "lcov-parse": "bin/cli.js" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "devOptional": true, - "peer": true - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "devOptional": true, - "peer": true - }, - "node_modules/log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true, - "engines": { - "node": ">=0.8.6" - } - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "devOptional": true, - "peer": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log4js": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", - "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", - "dev": true, - "dependencies": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "flatted": "^3.2.6", - "rfdc": "^1.3.0", - "streamroller": "^3.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "bin": { - "logkitty": "bin/logkitty.js" - } - }, - "node_modules/logkitty/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/logkitty/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/logkitty/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/logkitty/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/logkitty/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/logkitty/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "devOptional": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "devOptional": true, - "peer": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "devOptional": true, - "peer": true - }, - "node_modules/merge-options": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "devOptional": true, - "dependencies": { - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/metro": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.70.3.tgz", - "integrity": "sha512-uEWS7xg8oTetQDABYNtsyeUjdLhH3KAvLFpaFFoJqUpOk2A3iygszdqmjobFl6W4zrvKDJS+XxdMR1roYvUhTw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "absolute-path": "^0.0.0", - "accepts": "^1.3.7", - "async": "^3.2.2", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "error-stack-parser": "^2.0.6", - "fs-extra": "^1.0.0", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.6.0", - "image-size": "^0.6.0", - "invariant": "^2.2.4", - "jest-haste-map": "^27.3.1", - "jest-worker": "^27.2.0", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-config": "0.70.3", - "metro-core": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-inspector-proxy": "0.70.3", - "metro-minify-uglify": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-resolver": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "metro-symbolicate": "0.70.3", - "metro-transform-plugins": "0.70.3", - "metro-transform-worker": "0.70.3", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "rimraf": "^2.5.4", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", - "temp": "0.8.3", - "throat": "^5.0.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "bin": { - "metro": "src/cli.js" - } - }, - "node_modules/metro-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.70.3.tgz", - "integrity": "sha512-bWhZRMn+mIOR/s3BDpFevWScz9sV8FGktVfMlF1eJBLoX24itHDbXvTktKBYi38PWIKcHedh6THSFpJogfuwNA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "hermes-parser": "0.6.0", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "node_modules/metro-cache": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.70.3.tgz", - "integrity": "sha512-iCix/+z812fUqa6KlOxaTkY6LQQDoXIe/VljXkGIvpygSCmYyhjQpfQVZEVVPezFmUBYXNdabdQ6cYx6JX3yMg==", - "devOptional": true, - "peer": true, - "dependencies": { - "metro-core": "0.70.3", - "rimraf": "^2.5.4" - } - }, - "node_modules/metro-cache-key": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.70.3.tgz", - "integrity": "sha512-0zpw+IcpM3hmGd5sKMdxNv3sbOIUYnMUvx1/yaM6vNRReSPmOLX0bP8fYf3CGgk8NEreZ1OHbVsuw7bdKt40Mw==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-cache/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/metro-config": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.70.3.tgz", - "integrity": "sha512-SSCDjSTygoCgzoj61DdrBeJzZDRwQxUEfcgc6t6coxWSExXNR4mOngz0q4SAam49Bmjq9J2Jft6qUKnUTPrRgA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cosmiconfig": "^5.0.5", - "jest-validate": "^26.5.2", - "metro": "0.70.3", - "metro-cache": "0.70.3", - "metro-core": "0.70.3", - "metro-runtime": "0.70.3" - } - }, - "node_modules/metro-config/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/metro-config/node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/metro-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/metro-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-config/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/metro-config/node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/metro-config/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/metro-config/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-core": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.70.3.tgz", - "integrity": "sha512-NzfHB/w5R7yLaOeU1tzPTbBzCRsYSvpKJkLMP0yudszKZzIAZqNdjoEJ9GZ688Wi0ynZxcU0BxukXh4my80ZBw==", - "devOptional": true, - "peer": true, - "dependencies": { - "jest-haste-map": "^27.3.1", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.70.3" - } - }, - "node_modules/metro-core/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro-core/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/metro-core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro-core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/metro-core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro-core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-core/node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/metro-core/node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro-core/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro-core/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/metro-core/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/metro-core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-hermes-compiler": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.70.3.tgz", - "integrity": "sha512-W6WttLi4E72JL/NyteQ84uxYOFMibe0PUr9aBKuJxxfCq6QRnJKOVcNY0NLW0He2tneXGk+8ZsNz8c0flEvYqg==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.70.3.tgz", - "integrity": "sha512-qQoNdPGrmyoJSWYkxSDpTaAI8xyqVdNDVVj9KRm1PG8niSuYmrCCFGLLFsMvkVYwsCWUGHoGBx0UoAzVp14ejw==", - "devOptional": true, - "peer": true, - "dependencies": { - "connect": "^3.6.5", - "debug": "^2.2.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "bin": { - "metro-inspector-proxy": "src/cli.js" - } - }, - "node_modules/metro-inspector-proxy/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro-inspector-proxy/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/metro-inspector-proxy/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/metro-inspector-proxy/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro-inspector-proxy/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/metro-inspector-proxy/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/metro-inspector-proxy/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/metro-inspector-proxy/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/metro-minify-uglify": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.70.3.tgz", - "integrity": "sha512-oHyjV9WDqOlDE1FPtvs6tIjjeY/oP1PNUPYL1wqyYtqvjN+zzAOrcbsAAL1sv+WARaeiMsWkF2bwtNo+Hghoog==", - "devOptional": true, - "peer": true, - "dependencies": { - "uglify-es": "^3.1.9" - } - }, - "node_modules/metro-react-native-babel-preset": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", - "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.2.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-exponentiation-operator": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/metro-react-native-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz", - "integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.6.0", - "metro-babel-transformer": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/metro-resolver": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.70.3.tgz", - "integrity": "sha512-5Pc5S/Gs4RlLbziuIWtvtFd9GRoILlaRC8RZDVq5JZWcWHywKy/PjNmOBNhpyvtRlzpJfy/ssIfLhu8zINt1Mw==", - "devOptional": true, - "peer": true, - "dependencies": { - "absolute-path": "^0.0.0" - } - }, - "node_modules/metro-runtime": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.70.3.tgz", - "integrity": "sha512-22xU7UdXZacniTIDZgN2EYtmfau2pPyh97Dcs+cWrLcJYgfMKjWBtesnDcUAQy3PHekDYvBdJZkoQUeskYTM+w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.0.0" - } - }, - "node_modules/metro-source-map": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", - "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.70.3", - "nullthrows": "^1.1.1", - "ob1": "0.70.3", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - } - }, - "node_modules/metro-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/metro-symbolicate": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", - "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", - "devOptional": true, - "peer": true, - "dependencies": { - "invariant": "^2.2.4", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.1", - "vlq": "^1.0.0" - }, - "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": ">=8.3" - } - }, - "node_modules/metro-symbolicate/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/metro-transform-plugins": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.70.3.tgz", - "integrity": "sha512-dQRIJoTkWZN2IVS2KzgS1hs7ZdHDX3fS3esfifPkqFAEwHiLctCf0EsPgIknp0AjMLvmGWfSLJigdRB/dc0ASw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "nullthrows": "^1.1.1" - } - }, - "node_modules/metro-transform-worker": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.70.3.tgz", - "integrity": "sha512-MtVVsnHhhBOp9GRLCdAb2mD1dTCsIzT4+m34KMRdBDCEbDIb90YafT5prpU8qbj5uKd0o2FOQdrJ5iy5zQilHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.0.0", - "babel-preset-fbjs": "^3.4.0", - "metro": "0.70.3", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-source-map": "0.70.3", - "metro-transform-plugins": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "node_modules/metro/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/metro/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/metro/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/metro/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/metro/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "node_modules/metro/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/metro/node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro/node_modules/jest-util/node_modules/ci-info": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", - "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/metro/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/metro/node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "devOptional": true, - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/metro/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/metro/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/metro/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/metro/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/metro/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "devOptional": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "devOptional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "devOptional": true - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "devOptional": true, - "peer": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "devOptional": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/mocha-lcov-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", - "dev": true - }, - "node_modules/mocha/node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dev": true, - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true - }, - "node_modules/murmurhash": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", - "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "devOptional": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "devOptional": true, - "peer": true - }, - "node_modules/nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "dependencies": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/nise/node_modules/lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/nock": { - "version": "11.9.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", - "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.13", - "mkdirp": "^0.5.0", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "devOptional": true, - "peer": true, - "dependencies": { - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.10.5" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "devOptional": true, - "peer": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "devOptional": true, - "peer": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "devOptional": true, - "peer": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "devOptional": true - }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "devOptional": true - }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "devOptional": true, - "peer": true - }, - "node_modules/nwsapi": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", - "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", - "dev": true - }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/nyc/node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/nyc/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/nyc/node_modules/default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nyc/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/ob1": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", - "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==", - "devOptional": true, - "peer": true - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "devOptional": true, - "peer": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "devOptional": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", - "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", - "dev": true, - "dependencies": { - "entities": "^4.3.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "devOptional": true - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-to-regexp/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", - "dev": true, - "dependencies": { - "through": "~2.3" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "devOptional": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/plist": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz", - "integrity": "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==", - "devOptional": true, - "peer": true, - "dependencies": { - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true, - "peer": true - }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/promise": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.2.0.tgz", - "integrity": "sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==", - "devOptional": true, - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/promise-polyfill": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", - "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", - "dev": true - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "devOptional": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "devOptional": true, - "peer": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz", - "integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==", - "devOptional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-devtools-core": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.24.0.tgz", - "integrity": "sha512-Rw7FzYOOzcfyUPaAm9P3g0tFdGqGq2LLiAI+wjYcp6CsF3DeeMrRS3HZAho4s273C29G/DJhx0e8BpRE/QZNGg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "devOptional": true - }, - "node_modules/react-native": { - "version": "0.69.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.5.tgz", - "integrity": "sha512-4Psrj1nDMLQjBXVH8n3UikzOHQc8+sa6NbxZQR0XKtpx8uC3HiJBgX+/FIum/RWxfi5J/Dt/+A2gLGmq2Hps8g==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/create-cache-key-function": "^27.0.1", - "@react-native-community/cli": "^8.0.4", - "@react-native-community/cli-platform-android": "^8.0.4", - "@react-native-community/cli-platform-ios": "^8.0.4", - "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "2.0.0", - "@react-native/polyfills": "2.0.0", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "base64-js": "^1.1.2", - "event-target-shim": "^5.0.1", - "hermes-engine": "~0.11.0", - "invariant": "^2.2.4", - "jsc-android": "^250230.2.1", - "memoize-one": "^5.0.0", - "metro-react-native-babel-transformer": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", - "promise": "^8.0.3", - "react-devtools-core": "4.24.0", - "react-native-codegen": "^0.69.2", - "react-native-gradle-plugin": "^0.0.7", - "react-refresh": "^0.4.0", - "react-shallow-renderer": "16.15.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "^0.21.0", - "stacktrace-parser": "^0.1.3", - "use-sync-external-store": "^1.0.0", - "whatwg-fetch": "^3.0.0", - "ws": "^6.1.4" - }, - "bin": { - "react-native": "cli.js" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "18.0.0" - } - }, - "node_modules/react-native-codegen": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/react-native-codegen/-/react-native-codegen-0.69.2.tgz", - "integrity": "sha512-yPcgMHD4mqLbckqnWjFBaxomDnBREfRjDi2G/WxNyPBQLD+PXUEmZTkDx6QoOXN+Bl2SkpnNOSsLE2+/RUHoPw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/parser": "^7.14.0", - "flow-parser": "^0.121.0", - "jscodeshift": "^0.13.1", - "nullthrows": "^1.1.1" - } - }, - "node_modules/react-native-gradle-plugin": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz", - "integrity": "sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g==", - "devOptional": true, - "peer": true - }, - "node_modules/react-native/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/react-native/node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/react-native/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-native/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-native/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/react-native/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "node_modules/react-native/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/react-native/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "node_modules/react-native/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "devOptional": true, - "peer": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/react-refresh": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "devOptional": true, - "peer": true, - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "devOptional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "devOptional": true, - "peer": true - }, - "node_modules/recast": { - "version": "0.20.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", - "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "devOptional": true, - "peer": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "peer": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "devOptional": true, - "peer": true - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "devOptional": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "devOptional": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "devOptional": true, - "peer": true - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "devOptional": true, - "peer": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", - "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", - "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.5.5", - "jest-worker": "^24.9.0", - "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^4.0.0", - "terser": "^4.6.2" - }, - "peerDependencies": { - "rollup": ">=0.66.0 <3" - } - }, - "node_modules/rollup-plugin-terser/node_modules/jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dev": true, - "dependencies": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rollup-plugin-terser/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/rollup-plugin-typescript2": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", - "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "8.1.0", - "resolve": "1.17.0", - "tslib": "2.0.1" - }, - "peerDependencies": { - "rollup": ">=1.26.3", - "typescript": ">=2.4.0" - } - }, - "node_modules/rollup-plugin-typescript2/node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rollup-plugin-typescript2/node_modules/tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", - "dev": true - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/rollup-pluginutils/node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "node_modules/rollup/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "devOptional": true, - "peer": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", - "dev": true - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", - "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "devOptional": true, - "peer": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, - "peer": true - }, - "node_modules/serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "devOptional": true, - "peer": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "devOptional": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "devOptional": true, - "peer": true - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true - }, - "node_modules/sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", - "dev": true, - "dependencies": { - "diff": "^3.1.0", - "formatio": "1.2.0", - "lolex": "^1.6.0", - "native-promise-only": "^0.8.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.1.103" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "devOptional": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "devOptional": true, - "peer": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "devOptional": true, - "peer": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/socket.io": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", - "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.2.0", - "socket.io-adapter": "~2.4.0", - "socket.io-parser": "~4.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", - "dev": true - }, - "node_modules/socket.io-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", - "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", - "dev": true, - "dependencies": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "devOptional": true, - "peer": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "devOptional": true, - "peer": true - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/spawn-wrap/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/spawn-wrap/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "devOptional": true - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "devOptional": true, - "peer": true - }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "devOptional": true, - "peer": true, - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "devOptional": true, - "peer": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1" - } - }, - "node_modules/streamroller": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.2.tgz", - "integrity": "sha512-wZswqzbgGGsXYIrBYhOE0yP+nQ6XRk7xDcYwuQAGTYXdyAUmvgVFE0YU1g5pvQT0m7GBaQfYcSnlHbapuK0H0A==", - "dev": true, - "dependencies": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "devOptional": true, - "peer": true - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "devOptional": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "devOptional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", - "devOptional": true, - "engines": [ - "node >=0.8.0" - ], - "peer": true, - "dependencies": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" - } - }, - "node_modules/temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", - "dev": true, - "dependencies": { - "rimraf": "~2.5.2" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/temp-fs/node_modules/rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", - "dev": true, - "dependencies": { - "glob": "^7.0.5" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "devOptional": true, - "peer": true, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", - "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.7", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.7.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", - "deprecated": "no longer maintained", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "devOptional": true, - "peer": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "devOptional": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/through2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": true - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "devOptional": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "devOptional": true - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "devOptional": true, - "peer": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "devOptional": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^28.0.0", - "babel-jest": "^28.0.0", - "jest": "^28.0.0", - "typescript": ">=4.3" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/ts-loader": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.1.tgz", - "integrity": "sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-loader/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ts-loader/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.5" - } - }, - "node_modules/ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ts-node/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "devOptional": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" - } - }, - "node_modules/uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "deprecated": "support for ECMAScript is superseded by `uglify-js` as of v3.13.0", - "devOptional": true, - "peer": true, - "dependencies": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uglify-es/node_modules/commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "devOptional": true, - "peer": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "devOptional": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "devOptional": true, - "peer": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "devOptional": true, - "peer": true - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "devOptional": true, - "peer": true, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true, - "peer": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "devOptional": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "devOptional": true, - "peer": true - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", - "dev": true, - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "devOptional": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "devOptional": true, - "peer": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", - "devOptional": true, - "peer": true - }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "devOptional": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "devOptional": true - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "devOptional": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "devOptional": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", - "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", - "devOptional": true - }, - "@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "devOptional": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "dependencies": { - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "devOptional": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true - } - } - }, - "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "devOptional": true, - "requires": { - "@babel/types": "^7.19.0", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "devOptional": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "devOptional": true - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", - "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", - "devOptional": true, - "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "devOptional": true - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "devOptional": true, - "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "devOptional": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "devOptional": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", - "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "devOptional": true - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "devOptional": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "devOptional": true - }, - "@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "devOptional": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "devOptional": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "devOptional": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", - "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", - "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz", - "integrity": "sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz", - "integrity": "sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", - "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz", - "integrity": "sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz", - "integrity": "sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz", - "integrity": "sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.18.6" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.1.tgz", - "integrity": "sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-compilation-targets": "^7.19.1", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.9", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.18.13", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/preset-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.18.6.tgz", - "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-flow-strip-types": "^7.18.6" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - } - }, - "@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "devOptional": true, - "peer": true, - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "devOptional": true, - "peer": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "devOptional": true, - "peer": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "devOptional": true, - "peer": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "peer": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "devOptional": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "devOptional": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true - } - } - }, - "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "devOptional": true, - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "devOptional": true - } - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "devOptional": true, - "peer": true - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "devOptional": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/create-cache-key-function": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", - "integrity": "sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", - "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/node": "*", - "jest-mock": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "requires": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - } - }, - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "@jest/fake-timers": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", - "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.0.1", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } - } - }, - "@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "devOptional": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "devOptional": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "devOptional": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@optimizely/js-sdk-datafile-manager": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-datafile-manager/-/js-sdk-datafile-manager-0.9.5.tgz", - "integrity": "sha512-O4ujr1nBBAQBtx8YoKNpzzaEZgsE+aU4dxubT17ePqv/YVUWE+JOY21tSRrqZy/BlbbyzL+ElT8hrGB5ZzVoIQ==", - "requires": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0", - "decompress-response": "^4.2.1" - } - }, - "@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "requires": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", - "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", - "devOptional": true, - "requires": { - "merge-options": "^3.0.4" - } - }, - "@react-native-community/cli": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz", - "integrity": "sha512-E36hU/if3quQCfJHGWVkpsCnwtByRCwORuAX0r6yr1ebKktpKeEO49zY9PAu/Z1gfyxCtgluXY0HfRxjKRFXTg==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-clean": "^8.0.4", - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-doctor": "^8.0.6", - "@react-native-community/cli-hermes": "^8.0.5", - "@react-native-community/cli-plugin-metro": "^8.0.4", - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "@react-native-community/cli-types": "^8.0.0", - "chalk": "^4.1.2", - "commander": "^2.19.0", - "execa": "^1.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "minimist": "^1.2.0", - "prompts": "^2.4.0", - "semver": "^6.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - } - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-clean": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz", - "integrity": "sha512-IwS1M1NHg6+qL8PThZYMSIMYbZ6Zbx+lIck9PLBskbosFo24M3lCOflOl++Bggjakp6mR+sRXxLMexid/GeOsQ==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "prompts": "^2.4.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-config": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-8.0.6.tgz", - "integrity": "sha512-mjVpVvdh8AviiO8xtqeX+BkjqE//NMDnISwsLWSJUfNCwTAPmdR8PGbhgP5O4hWHyJ3WkepTopl0ya7Tfi3ifw==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "glob": "^7.1.3", - "joi": "^17.2.1" - }, - "dependencies": { - "deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", - "devOptional": true, - "peer": true - } - } - }, - "@react-native-community/cli-debugger-ui": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-8.0.0.tgz", - "integrity": "sha512-u2jq06GZwZ9sRERzd9FIgpW6yv4YOW4zz7Ym/B8eSzviLmy3yI/8mxJtvlGW+J8lBsfMcQoqJpqI6Rl1nZy9yQ==", - "devOptional": true, - "peer": true, - "requires": { - "serve-static": "^1.13.1" - } - }, - "@react-native-community/cli-doctor": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-8.0.6.tgz", - "integrity": "sha512-ZQqyT9mJMVeFEVIwj8rbDYGCA2xXjJfsQjWk2iTRZ1CFHfhPSUuUiG8r6mJmTinAP9t+wYcbbIYzNgdSUKnDMw==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-platform-ios": "^8.0.6", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "envinfo": "^7.7.2", - "execa": "^1.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "prompts": "^2.4.0", - "semver": "^6.3.0", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^9.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - } - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-hermes": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-8.0.5.tgz", - "integrity": "sha512-Zm0wM6SfgYAEX1kfJ1QBvTayabvh79GzmjHyuSnEROVNPbl4PeCG4WFbwy489tGwOP9Qx9fMT5tRIFCD8bp6/g==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-platform-android": "^8.0.5", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-platform-android": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-8.0.5.tgz", - "integrity": "sha512-z1YNE4T1lG5o9acoQR1GBvf7mq6Tzayqo/za5sHVSOJAC9SZOuVN/gg/nkBa9a8n5U7qOMFXfwhTMNqA474gXA==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.3", - "jetifier": "^1.6.2", - "lodash": "^4.17.15", - "logkitty": "^0.7.1", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-platform-ios": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-8.0.6.tgz", - "integrity": "sha512-CMR6mu/LVx6JVfQRDL9uULsMirJT633bODn+IrYmrwSz250pnhON16We8eLPzxOZHyDjm7JPuSgHG3a/BPiRuQ==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "glob": "^7.1.3", - "js-yaml": "^3.13.1", - "lodash": "^4.17.15", - "ora": "^5.4.1", - "plist": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-plugin-metro": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-8.0.4.tgz", - "integrity": "sha512-UWzY1eMcEr/6262R2+d0Is5M3L/7Y/xXSDIFMoc5Rv5Wucl3hJM/TxHXmByvHpuJf6fJAfqOskyt4bZCvbI+wQ==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "metro": "^0.70.1", - "metro-config": "^0.70.1", - "metro-core": "^0.70.1", - "metro-react-native-babel-transformer": "^0.70.1", - "metro-resolver": "^0.70.1", - "metro-runtime": "^0.70.1", - "readline": "^1.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-server-api": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-8.0.4.tgz", - "integrity": "sha512-Orr14njx1E70CVrUA8bFdl+mrnbuXUjf1Rhhm0RxUadFpvkHuOi5dh8Bryj2MKtf8eZrpEwZ7tuQPhJEULW16A==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-tools": "^8.0.4", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.0", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - } - } - }, - "@react-native-community/cli-tools": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-8.0.4.tgz", - "integrity": "sha512-ePN9lGxh6LRFiotyddEkSmuqpQhnq2iw9oiXYr4EFWpIEy0yCigTuSTiDF68+c8M9B+7bTwkRpz/rMPC4ViO5Q==", - "devOptional": true, - "peer": true, - "requires": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "lodash": "^4.17.15", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^6.3.0", - "shell-quote": "^1.7.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-8.0.0.tgz", - "integrity": "sha512-1lZS1PEvMlFaN3Se1ksyoFWzMjk+YfKi490GgsqKJln9gvFm8tqVPdnXttI5Uf2DQf3BMse8Bk8dNH4oV6Ewow==", - "devOptional": true, - "peer": true, - "requires": { - "joi": "^17.2.1" - } - }, - "@react-native-community/netinfo": { - "version": "5.9.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", - "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", - "dev": true, - "requires": {} - }, - "@react-native/assets": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz", - "integrity": "sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==", - "devOptional": true, - "peer": true - }, - "@react-native/normalize-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.0.0.tgz", - "integrity": "sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw==", - "devOptional": true, - "peer": true - }, - "@react-native/polyfills": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/polyfills/-/polyfills-2.0.0.tgz", - "integrity": "sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==", - "devOptional": true, - "peer": true - }, - "@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - } - }, - "@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "devOptional": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", - "devOptional": true, - "peer": true - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "devOptional": true, - "peer": true - }, - "@sinclair/typebox": { - "version": "0.24.34", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", - "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", - "dev": true - }, - "@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "@types/eslint": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", - "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "devOptional": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "devOptional": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "devOptional": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "devOptional": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "@types/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", - "dev": true - }, - "@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", - "devOptional": true - }, - "@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", - "dev": true - }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", - "dev": true - }, - "@types/uuid": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", - "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", - "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "devOptional": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", - "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/type-utils": "5.33.0", - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", - "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", - "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", - "dev": true, - "requires": { - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", - "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "devOptional": true, - "peer": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "absolute-path": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==", - "devOptional": true, - "peer": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "devOptional": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "devOptional": true, - "peer": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "devOptional": true, - "peer": true, - "requires": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "devOptional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "devOptional": true, - "peer": true - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "devOptional": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "devOptional": true, - "peer": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "devOptional": true, - "peer": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "devOptional": true, - "peer": true - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "devOptional": true, - "peer": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "devOptional": true, - "peer": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "devOptional": true, - "peer": true - }, - "ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "devOptional": true, - "peer": true, - "requires": { - "tslib": "^2.0.1" - } - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "devOptional": true, - "peer": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "devOptional": true, - "peer": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "devOptional": true, - "peer": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "devOptional": true, - "peer": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", - "dev": true, - "requires": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "devOptional": true, - "peer": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - } - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "7.0.0-beta.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", - "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", - "devOptional": true, - "peer": true - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-fbjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", - "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-class-properties": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-member-expression-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-property-literals": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" - } - }, - "babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "devOptional": true, - "peer": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "devOptional": true, - "peer": true - }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, - "peer": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "devOptional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "devOptional": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - } - }, - "browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - } - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "devOptional": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, - "peer": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "devOptional": true, - "peer": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "devOptional": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "devOptional": true, - "peer": true, - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "devOptional": true, - "peer": true - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "devOptional": true, - "peer": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "devOptional": true - }, - "caniuse-lite": { - "version": "1.0.30001407", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", - "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", - "devOptional": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "devOptional": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "devOptional": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "devOptional": true, - "peer": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "devOptional": true, - "peer": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "devOptional": true, - "peer": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "devOptional": true, - "peer": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "devOptional": true, - "peer": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "devOptional": true, - "peer": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "devOptional": true, - "peer": true - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "devOptional": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "devOptional": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "devOptional": true, - "peer": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "devOptional": true, - "peer": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": true - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "devOptional": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true - } - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "devOptional": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true - } - } - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "devOptional": true, - "peer": true - }, - "core-js-compat": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.2.tgz", - "integrity": "sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==", - "devOptional": true, - "peer": true, - "requires": { - "browserslist": "^4.21.4" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "devOptional": true - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "devOptional": true, - "peer": true, - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "dependencies": { - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "devOptional": true, - "peer": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "devOptional": true, - "peer": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "devOptional": true, - "peer": true - } - } - }, - "coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - } - }, - "date-format": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", - "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", - "dev": true - }, - "dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "devOptional": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "devOptional": true - }, - "decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "devOptional": true, - "peer": true - }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "devOptional": true, - "peer": true, - "requires": { - "clone": "^1.0.2" - } - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "devOptional": true, - "peer": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "devOptional": true, - "peer": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "devOptional": true - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "devOptional": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "requires": { - "webidl-conversions": "^7.0.0" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "devOptional": true - }, - "electron-to-chromium": { - "version": "1.4.256", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", - "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", - "devOptional": true - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "devOptional": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "devOptional": true, - "peer": true, - "requires": { - "once": "^1.4.0" - } - }, - "engine.io": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", - "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", - "dev": true, - "requires": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - }, - "dependencies": { - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "requires": {} - } - } - }, - "engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true - }, - "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "devOptional": true, - "peer": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "devOptional": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "devOptional": true, - "peer": true, - "requires": { - "stackframe": "^1.3.4" - } - }, - "errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "devOptional": true, - "peer": true, - "requires": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - } - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "devOptional": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "devOptional": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", - "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.3", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "devOptional": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "devOptional": true, - "peer": true - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "devOptional": true, - "peer": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "devOptional": true, - "peer": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - } - } - }, - "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "devOptional": true, - "peer": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "devOptional": true, - "peer": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "devOptional": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "devOptional": true, - "requires": { - "to-regex-range": "^5.0.1" - }, - "dependencies": { - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "devOptional": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "devOptional": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "devOptional": true - } - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "dependencies": { - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true - } - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "flow-parser": { - "version": "0.121.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz", - "integrity": "sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==", - "devOptional": true, - "peer": true - }, - "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "devOptional": true, - "peer": true - }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", - "dev": true, - "requires": { - "samsam": "1.x" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "devOptional": true, - "peer": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "devOptional": true, - "peer": true - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, - "fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true - }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "devOptional": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "devOptional": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "devOptional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "devOptional": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "devOptional": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "devOptional": true, - "peer": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "devOptional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "devOptional": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "devOptional": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "devOptional": true, - "peer": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "devOptional": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "devOptional": true, - "peer": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - } - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", - "dev": true - }, - "hermes-engine": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz", - "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw==", - "devOptional": true, - "peer": true - }, - "hermes-estree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.6.0.tgz", - "integrity": "sha512-2YTGzJCkhdmT6VuNprWjXnvTvw/3iPNw804oc7yknvQpNKo+vJGZmtvLLCghOZf0OwzKaNAzeIMp71zQbNl09w==", - "devOptional": true, - "peer": true - }, - "hermes-parser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", - "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", - "devOptional": true, - "peer": true, - "requires": { - "hermes-estree": "0.6.0" - } - }, - "hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "devOptional": true, - "peer": true, - "requires": { - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "devOptional": true, - "peer": true - } - } - }, - "html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "requires": { - "whatwg-encoding": "^2.0.0" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "devOptional": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, - "peer": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "image-size": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", - "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", - "devOptional": true, - "peer": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "devOptional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "devOptional": true, - "peer": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", - "devOptional": true, - "peer": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "devOptional": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "devOptional": true, - "peer": true - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "devOptional": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "devOptional": true, - "peer": true - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "devOptional": true, - "peer": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "devOptional": true, - "peer": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "devOptional": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "devOptional": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "devOptional": true, - "peer": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "devOptional": true, - "peer": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "devOptional": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "devOptional": true, - "peer": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "devOptional": true, - "peer": true - }, - "isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "devOptional": true, - "peer": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", - "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", - "dev": true, - "requires": { - "@jest/environment": "^29.0.1", - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1", - "jsdom": "^20.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-localstorage-mock": { - "version": "2.4.22", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", - "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", - "dev": true - }, - "jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", - "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", - "dev": true, - "requires": { - "@jest/types": "^29.0.3", - "@types/node": "*" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", - "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true - }, - "jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "requires": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - } - }, - "jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "devOptional": true, - "peer": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - } - }, - "jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jetifier": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.8.tgz", - "integrity": "sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw==", - "devOptional": true, - "peer": true - }, - "joi": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", - "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", - "devOptional": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "devOptional": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "devOptional": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "jsc-android": { - "version": "250230.2.1", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250230.2.1.tgz", - "integrity": "sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q==", - "devOptional": true, - "peer": true - }, - "jscodeshift": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.13.1.tgz", - "integrity": "sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "devOptional": true, - "peer": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "devOptional": true, - "peer": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "devOptional": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "devOptional": true, - "peer": true, - "requires": { - "rimraf": "~2.6.2" - } - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, - "jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.8.0", - "xml-name-validator": "^4.0.0" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "devOptional": true, - "peer": true - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "devOptional": true, - "peer": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://nexus.es.ecg.tools/repository/npm-all/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "devOptional": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "karma": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", - "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - } - } - }, - "karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", - "dev": true, - "requires": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" - } - }, - "karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true, - "requires": {} - }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", - "dev": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", - "dev": true - } - } - }, - "karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "webpack-merge": "^4.1.5" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "devOptional": true, - "peer": true - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "devOptional": true - }, - "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "devOptional": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "devOptional": true, - "peer": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "devOptional": true, - "peer": true - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "devOptional": true, - "peer": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "log4js": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", - "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", - "dev": true, - "requires": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "flatted": "^3.2.6", - "rfdc": "^1.3.0", - "streamroller": "^3.1.2" - } - }, - "logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "devOptional": true, - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "devOptional": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "devOptional": true, - "peer": true - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "devOptional": true, - "peer": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true - }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "devOptional": true, - "peer": true - }, - "merge-options": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "devOptional": true, - "requires": { - "is-plain-obj": "^2.1.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "metro": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.70.3.tgz", - "integrity": "sha512-uEWS7xg8oTetQDABYNtsyeUjdLhH3KAvLFpaFFoJqUpOk2A3iygszdqmjobFl6W4zrvKDJS+XxdMR1roYvUhTw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "absolute-path": "^0.0.0", - "accepts": "^1.3.7", - "async": "^3.2.2", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "error-stack-parser": "^2.0.6", - "fs-extra": "^1.0.0", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.6.0", - "image-size": "^0.6.0", - "invariant": "^2.2.4", - "jest-haste-map": "^27.3.1", - "jest-worker": "^27.2.0", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-config": "0.70.3", - "metro-core": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-inspector-proxy": "0.70.3", - "metro-minify-uglify": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-resolver": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "metro-symbolicate": "0.70.3", - "metro-transform-plugins": "0.70.3", - "metro-transform-worker": "0.70.3", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "rimraf": "^2.5.4", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", - "temp": "0.8.3", - "throat": "^5.0.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "devOptional": true, - "peer": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ci-info": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", - "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", - "devOptional": true, - "peer": true - } - } - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "metro-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.70.3.tgz", - "integrity": "sha512-bWhZRMn+mIOR/s3BDpFevWScz9sV8FGktVfMlF1eJBLoX24itHDbXvTktKBYi38PWIKcHedh6THSFpJogfuwNA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "hermes-parser": "0.6.0", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "metro-cache": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.70.3.tgz", - "integrity": "sha512-iCix/+z812fUqa6KlOxaTkY6LQQDoXIe/VljXkGIvpygSCmYyhjQpfQVZEVVPezFmUBYXNdabdQ6cYx6JX3yMg==", - "devOptional": true, - "peer": true, - "requires": { - "metro-core": "0.70.3", - "rimraf": "^2.5.4" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "metro-cache-key": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.70.3.tgz", - "integrity": "sha512-0zpw+IcpM3hmGd5sKMdxNv3sbOIUYnMUvx1/yaM6vNRReSPmOLX0bP8fYf3CGgk8NEreZ1OHbVsuw7bdKt40Mw==", - "devOptional": true, - "peer": true - }, - "metro-config": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.70.3.tgz", - "integrity": "sha512-SSCDjSTygoCgzoj61DdrBeJzZDRwQxUEfcgc6t6coxWSExXNR4mOngz0q4SAam49Bmjq9J2Jft6qUKnUTPrRgA==", - "devOptional": true, - "peer": true, - "requires": { - "cosmiconfig": "^5.0.5", - "jest-validate": "^26.5.2", - "metro": "0.70.3", - "metro-cache": "0.70.3", - "metro-core": "0.70.3", - "metro-runtime": "0.70.3" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "devOptional": true, - "peer": true - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "metro-core": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.70.3.tgz", - "integrity": "sha512-NzfHB/w5R7yLaOeU1tzPTbBzCRsYSvpKJkLMP0yudszKZzIAZqNdjoEJ9GZ688Wi0ynZxcU0BxukXh4my80ZBw==", - "devOptional": true, - "peer": true, - "requires": { - "jest-haste-map": "^27.3.1", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.70.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "metro-hermes-compiler": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.70.3.tgz", - "integrity": "sha512-W6WttLi4E72JL/NyteQ84uxYOFMibe0PUr9aBKuJxxfCq6QRnJKOVcNY0NLW0He2tneXGk+8ZsNz8c0flEvYqg==", - "devOptional": true, - "peer": true - }, - "metro-inspector-proxy": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.70.3.tgz", - "integrity": "sha512-qQoNdPGrmyoJSWYkxSDpTaAI8xyqVdNDVVj9KRm1PG8niSuYmrCCFGLLFsMvkVYwsCWUGHoGBx0UoAzVp14ejw==", - "devOptional": true, - "peer": true, - "requires": { - "connect": "^3.6.5", - "debug": "^2.2.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" } }, - "metro-minify-uglify": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.70.3.tgz", - "integrity": "sha512-oHyjV9WDqOlDE1FPtvs6tIjjeY/oP1PNUPYL1wqyYtqvjN+zzAOrcbsAAL1sv+WARaeiMsWkF2bwtNo+Hghoog==", - "devOptional": true, - "peer": true, - "requires": { - "uglify-es": "^3.1.9" - } - }, - "metro-react-native-babel-preset": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", - "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.2.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-exponentiation-operator": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - } - }, - "metro-react-native-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz", - "integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.6.0", - "metro-babel-transformer": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "metro-resolver": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.70.3.tgz", - "integrity": "sha512-5Pc5S/Gs4RlLbziuIWtvtFd9GRoILlaRC8RZDVq5JZWcWHywKy/PjNmOBNhpyvtRlzpJfy/ssIfLhu8zINt1Mw==", - "devOptional": true, - "peer": true, - "requires": { - "absolute-path": "^0.0.0" - } - }, - "metro-runtime": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.70.3.tgz", - "integrity": "sha512-22xU7UdXZacniTIDZgN2EYtmfau2pPyh97Dcs+cWrLcJYgfMKjWBtesnDcUAQy3PHekDYvBdJZkoQUeskYTM+w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.0.0" - } - }, - "metro-source-map": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", - "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.70.3", - "nullthrows": "^1.1.1", - "ob1": "0.70.3", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true - } + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" } }, - "metro-symbolicate": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", - "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", - "devOptional": true, - "peer": true, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "requires": { - "invariant": "^2.2.4", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.1", - "vlq": "^1.0.0" + "semver": "^6.0.0" }, "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, - "metro-transform-plugins": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.70.3.tgz", - "integrity": "sha512-dQRIJoTkWZN2IVS2KzgS1hs7ZdHDX3fS3esfifPkqFAEwHiLctCf0EsPgIknp0AjMLvmGWfSLJigdRB/dc0ASw==", - "devOptional": true, - "peer": true, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, "requires": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "nullthrows": "^1.1.1" + "tmpl": "1.0.5" } }, - "metro-transform-worker": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.70.3.tgz", - "integrity": "sha512-MtVVsnHhhBOp9GRLCdAb2mD1dTCsIzT4+m34KMRdBDCEbDIb90YafT5prpU8qbj5uKd0o2FOQdrJ5iy5zQilHw==", - "devOptional": true, - "peer": true, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dev": true, "requires": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.0.0", - "babel-preset-fbjs": "^3.4.0", - "metro": "0.70.3", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-source-map": "0.70.3", - "metro-transform-plugins": "0.70.3", - "nullthrows": "^1.1.1" + "is-plain-obj": "^2.1.0" } }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "devOptional": true, + "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -32795,19 +6555,19 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "devOptional": true + "dev": true }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true + "dev": true }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, + "dev": true, "requires": { "mime-db": "1.52.0" } @@ -32816,7 +6576,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true + "dev": true }, "mimic-response": { "version": "2.1.0", @@ -32827,7 +6587,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -32836,24 +6596,13 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "devOptional": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "devOptional": true, - "peer": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } + "dev": true }, "mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "devOptional": true, + "dev": true, "requires": { "minimist": "^1.2.6" } @@ -32912,13 +6661,13 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -32927,7 +6676,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "supports-color": { @@ -32944,70 +6693,43 @@ "mocha-lcov-reporter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", + "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", "dev": true }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true + "dev": true }, "murmurhash": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "devOptional": true, - "peer": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", "dev": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "devOptional": true + "dev": true }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "devOptional": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "devOptional": true, - "peer": true + "dev": true }, "nise": { "version": "1.5.3", @@ -33033,13 +6755,6 @@ } } }, - "nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "devOptional": true, - "peer": true - }, "nock": { "version": "11.9.1", "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", @@ -33053,58 +6768,11 @@ "propagate": "^2.0.0" } }, - "node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "devOptional": true, - "peer": true, - "requires": { - "minimatch": "^3.0.2" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "devOptional": true, - "peer": true, - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "devOptional": true, - "peer": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "devOptional": true, - "peer": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "devOptional": true, - "peer": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "devOptional": true + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true }, "node-preload": { "version": "0.2.1", @@ -33119,20 +6787,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "devOptional": true - }, - "node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "devOptional": true, - "peer": true + "dev": true }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "devOptional": true + "dev": true }, "npm-run-path": { "version": "4.0.1", @@ -33146,16 +6807,9 @@ "null-check": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", "dev": true }, - "nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "devOptional": true, - "peer": true - }, "nwsapi": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", @@ -33395,292 +7049,64 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, - "ob1": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", - "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==", - "devOptional": true, - "peer": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "devOptional": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "devOptional": true, - "peer": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "devOptional": true, - "peer": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "devOptional": true, - "peer": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "devOptional": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "devOptional": true, - "peer": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "devOptional": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "devOptional": true, - "peer": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "devOptional": true, - "peer": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "devOptional": true, - "peer": true, - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "devOptional": true, - "peer": true + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "devOptional": true, - "peer": true + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, + "dev": true, "requires": { "yocto-queue": "^0.1.0" } @@ -33689,7 +7115,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, + "dev": true, "requires": { "p-limit": "^3.0.2" } @@ -33707,7 +7133,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "devOptional": true + "dev": true }, "package-hash": { "version": "4.0.0", @@ -33755,27 +7181,13 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "devOptional": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "devOptional": true, - "peer": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "devOptional": true, - "peer": true + "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "3.1.1", @@ -33787,7 +7199,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "devOptional": true + "dev": true }, "path-to-regexp": { "version": "1.8.0", @@ -33801,7 +7213,7 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true } } @@ -33815,7 +7227,7 @@ "pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { "through": "~2.3" @@ -33824,33 +7236,26 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true + "dev": true }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "devOptional": true, - "peer": true + "dev": true }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "devOptional": true + "dev": true }, "pkg-dir": { "version": "4.2.0", @@ -33906,24 +7311,6 @@ } } }, - "plist": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz", - "integrity": "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==", - "devOptional": true, - "peer": true, - "requires": { - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "devOptional": true, - "peer": true - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -33965,13 +7352,6 @@ } } }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true, - "peer": true - }, "process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -33981,16 +7361,6 @@ "fromentries": "^1.2.0" } }, - "promise": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.2.0.tgz", - "integrity": "sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==", - "devOptional": true, - "peer": true, - "requires": { - "asap": "~2.0.6" - } - }, "promise-polyfill": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", @@ -34001,7 +7371,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "devOptional": true, + "dev": true, "requires": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -34028,17 +7398,6 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "devOptional": true, - "peer": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -34048,7 +7407,7 @@ "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, "qjobs": { @@ -34088,7 +7447,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "devOptional": true + "dev": true }, "raw-body": { "version": "2.5.1", @@ -34102,245 +7461,11 @@ "unpipe": "1.0.0" } }, - "react": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz", - "integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==", - "devOptional": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-devtools-core": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.24.0.tgz", - "integrity": "sha512-Rw7FzYOOzcfyUPaAm9P3g0tFdGqGq2LLiAI+wjYcp6CsF3DeeMrRS3HZAho4s273C29G/DJhx0e8BpRE/QZNGg==", - "devOptional": true, - "peer": true, - "requires": { - "shell-quote": "^1.6.1", - "ws": "^7" - }, - "dependencies": { - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - } - } - }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "devOptional": true - }, - "react-native": { - "version": "0.69.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.5.tgz", - "integrity": "sha512-4Psrj1nDMLQjBXVH8n3UikzOHQc8+sa6NbxZQR0XKtpx8uC3HiJBgX+/FIum/RWxfi5J/Dt/+A2gLGmq2Hps8g==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/create-cache-key-function": "^27.0.1", - "@react-native-community/cli": "^8.0.4", - "@react-native-community/cli-platform-android": "^8.0.4", - "@react-native-community/cli-platform-ios": "^8.0.4", - "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "2.0.0", - "@react-native/polyfills": "2.0.0", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "base64-js": "^1.1.2", - "event-target-shim": "^5.0.1", - "hermes-engine": "~0.11.0", - "invariant": "^2.2.4", - "jsc-android": "^250230.2.1", - "memoize-one": "^5.0.0", - "metro-react-native-babel-transformer": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", - "promise": "^8.0.3", - "react-devtools-core": "4.24.0", - "react-native-codegen": "^0.69.2", - "react-native-gradle-plugin": "^0.0.7", - "react-refresh": "^0.4.0", - "react-shallow-renderer": "16.15.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "^0.21.0", - "stacktrace-parser": "^0.1.3", - "use-sync-external-store": "^1.0.0", - "whatwg-fetch": "^3.0.0", - "ws": "^6.1.4" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "devOptional": true, - "peer": true, - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "react-native-codegen": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/react-native-codegen/-/react-native-codegen-0.69.2.tgz", - "integrity": "sha512-yPcgMHD4mqLbckqnWjFBaxomDnBREfRjDi2G/WxNyPBQLD+PXUEmZTkDx6QoOXN+Bl2SkpnNOSsLE2+/RUHoPw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/parser": "^7.14.0", - "flow-parser": "^0.121.0", - "jscodeshift": "^0.13.1", - "nullthrows": "^1.1.1" - } - }, - "react-native-gradle-plugin": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz", - "integrity": "sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g==", - "devOptional": true, - "peer": true - }, - "react-refresh": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", - "devOptional": true, - "peer": true - }, - "react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "devOptional": true, - "peer": true, - "requires": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "devOptional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } + "dev": true }, "readdirp": { "version": "3.6.0", @@ -34351,131 +7476,21 @@ "picomatch": "^2.2.1" } }, - "readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "devOptional": true, - "peer": true - }, - "recast": { - "version": "0.20.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", - "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", - "devOptional": true, - "peer": true, - "requires": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "devOptional": true, - "peer": true - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "devOptional": true, - "peer": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "peer": true - }, - "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, - "regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "devOptional": true, - "peer": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "devOptional": true, - "peer": true - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "devOptional": true, - "peer": true, - "requires": { - "jsesc": "~0.5.0" - } - }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { "es6-error": "^4.0.1" } }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "devOptional": true, - "peer": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "devOptional": true, - "peer": true - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -34515,26 +7530,26 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "devOptional": true + "dev": true }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "devOptional": true, + "dev": true, "requires": { "is-core-module": "^2.9.0", "path-parse": "^1.0.7", @@ -34564,37 +7579,12 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "devOptional": true, - "peer": true - }, "resolve.exports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", "dev": true }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "devOptional": true, - "peer": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "devOptional": true, - "peer": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -34728,17 +7718,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "devOptional": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "devOptional": true, - "peer": true, - "requires": { - "ret": "~0.1.10" - } + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -34761,16 +7741,6 @@ "xmlchars": "^2.2.0" } }, - "scheduler": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", - "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", - "devOptional": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -34791,70 +7761,6 @@ "lru-cache": "^6.0.0" } }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "devOptional": true, - "peer": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "devOptional": true, - "peer": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, - "peer": true - } - } - }, - "serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "devOptional": true, - "peer": true - }, "serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -34864,72 +7770,17 @@ "randombytes": "^2.1.0" } }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "devOptional": true, - "peer": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - } - } + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "devOptional": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^6.0.2" - } + "dev": true }, "shebang-command": { "version": "2.0.0", @@ -34946,13 +7797,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "devOptional": true, - "peer": true - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -34968,7 +7812,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true + "dev": true }, "sinon": { "version": "2.4.1", @@ -34990,213 +7834,13 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "devOptional": true + "dev": true }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "devOptional": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "devOptional": true, - "peer": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "devOptional": true, - "peer": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "devOptional": true, - "peer": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "dev": true }, "socket.io": { "version": "4.5.1", @@ -35233,21 +7877,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "devOptional": true, - "peer": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } + "dev": true }, "source-map-support": { "version": "0.5.13", @@ -35259,13 +7889,6 @@ "source-map": "^0.6.0" } }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "devOptional": true, - "peer": true - }, "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -35309,27 +7932,17 @@ "split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { "through": "2" } }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "devOptional": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { "version": "1.17.0", @@ -35365,128 +7978,16 @@ } } }, - "stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "devOptional": true, - "peer": true - }, - "stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "devOptional": true, - "peer": true, - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "devOptional": true, - "peer": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "devOptional": true, - "peer": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - } - } - }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "devOptional": true + "dev": true }, "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { "duplexer": "~0.1.1" @@ -35503,16 +8004,6 @@ "fs-extra": "^8.1.0" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "devOptional": true, - "peer": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -35527,7 +8018,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -35538,7 +8029,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -35549,13 +8040,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "devOptional": true, - "peer": true - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -35568,18 +8052,11 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "devOptional": true, - "peer": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "devOptional": true, + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -35615,7 +8092,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "devOptional": true + "dev": true }, "symbol-tree": { "version": "3.2.4", @@ -35629,30 +8106,10 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, - "temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", - "devOptional": true, - "peer": true, - "requires": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "devOptional": true, - "peer": true - } - } - }, "temp-fs": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", + "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", "dev": true, "requires": { "rimraf": "~2.5.2" @@ -35661,7 +8118,7 @@ "rimraf": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", "dev": true, "requires": { "glob": "^7.0.5" @@ -35812,153 +8269,32 @@ "text-encoding": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", "dev": true }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "devOptional": true, - "peer": true - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "devOptional": true, - "peer": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "devOptional": true, - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "devOptional": true, - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "devOptional": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "devOptional": true, - "peer": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "devOptional": true, - "peer": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "dev": true }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "devOptional": true + "dev": true }, "tough-cookie": { "version": "2.5.0", @@ -36116,7 +8452,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "devOptional": true + "dev": true }, "tsutils": { "version": "3.21.0", @@ -36138,7 +8474,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -36147,7 +8483,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, "type-check": { @@ -36202,141 +8538,23 @@ "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", "dev": true }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "devOptional": true, - "peer": true, - "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "devOptional": true, - "peer": true - } - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "devOptional": true, - "peer": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "devOptional": true, - "peer": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "devOptional": true, - "peer": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "devOptional": true, - "peer": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "devOptional": true, - "peer": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - } - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "devOptional": true + "dev": true }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "devOptional": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "devOptional": true, - "peer": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "devOptional": true, - "peer": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "devOptional": true, - "peer": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "devOptional": true, - "peer": true - } - } + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "update-browserslist-db": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "devOptional": true, + "dev": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -36351,13 +8569,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "devOptional": true, - "peer": true - }, "url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -36368,33 +8579,11 @@ "requires-port": "^1.0.0" } }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "devOptional": true, - "peer": true - }, - "use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true, - "peer": true - }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "devOptional": true + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true }, "uuid": { "version": "8.3.2", @@ -36422,12 +8611,12 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "devOptional": true + "dev": true }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -36435,17 +8624,10 @@ "extsprintf": "^1.2.0" } }, - "vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "devOptional": true, - "peer": true - }, "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, "w3c-hr-time": { @@ -36470,7 +8652,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "devOptional": true, + "dev": true, "requires": { "makeerror": "1.0.12" } @@ -36485,16 +8667,6 @@ "graceful-fs": "^4.1.2" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "devOptional": true, - "peer": true, - "requires": { - "defaults": "^1.0.3" - } - }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -36576,13 +8748,6 @@ } } }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", - "devOptional": true, - "peer": true - }, "whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -36603,7 +8768,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "devOptional": true, + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -36611,8 +8776,8 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "devOptional": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "word-wrap": { "version": "1.2.3", @@ -36660,8 +8825,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { "version": "4.0.2", @@ -36677,8 +8842,7 @@ "version": "8.8.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true, - "requires": {} + "dev": true }, "xml-name-validator": { "version": "4.0.0", @@ -36686,26 +8850,12 @@ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true }, - "xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "devOptional": true, - "peer": true - }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "devOptional": true, - "peer": true - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -36749,7 +8899,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true + "dev": true } } } diff --git a/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts b/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts index 49492a8c6..b9dabfd4b 100644 --- a/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; +import { NodeOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; import { OdpEvent } from '../lib/core/odp/odp_event'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; @@ -36,7 +36,7 @@ const ODP_EVENTS = [ new OdpEvent('t2', 'a2', new Map([['id-key-2', 'id-value-2']]), data2), ]; -describe('OdpEventApiManager', () => { +describe('NodeOdpEventApiManager', () => { let mockLogger: LogHandler; let mockRequestHandler: RequestHandler; @@ -50,7 +50,7 @@ describe('OdpEventApiManager', () => { resetCalls(mockRequestHandler); }); - const managerInstance = () => new OdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); + const managerInstance = () => new NodeOdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); const abortableRequest = (statusCode: number, body: string) => { return { abort: () => {}, diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index 944dc156e..7321cfc47 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -17,9 +17,10 @@ import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE } from './../lib/utils/enums/index'; import { OdpConfig } from '../lib/core/odp/odp_config'; -import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; +import { STATE } from '../lib/core/odp/odp_event_manager'; +import { NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; -import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; +import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; import { LogHandler, LogLevel } from '../lib/modules/logging'; import { OdpEvent } from '../lib/core/odp/odp_event'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts index 17514d890..b95226110 100644 --- a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -26,8 +26,8 @@ import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; import { BrowserOdpManager } from './../lib/plugins/odp_manager/index.browser'; import { OdpOptions } from './../lib/shared_types'; import { OdpConfig } from '../lib/core/odp/odp_config'; -import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; -import { OdpEventManager, STATE } from '../lib/core/odp/odp_event_manager'; +import { BrowserOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.browser'; +import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.browser'; import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; @@ -57,11 +57,11 @@ describe('OdpManager', () => { let mockRequestHandler: RequestHandler; let fakeRequestHandler: RequestHandler; - let mockEventApiManager: OdpEventApiManager; - let fakeEventApiManager: OdpEventApiManager; + let mockEventApiManager: BrowserOdpEventApiManager; + let fakeEventApiManager: BrowserOdpEventApiManager; - let mockEventManager: OdpEventManager; - let fakeEventManager: OdpEventManager; + let mockEventManager: BrowserOdpEventManager; + let fakeEventManager: BrowserOdpEventManager; let mockSegmentApiManager: OdpSegmentApiManager; let fakeSegmentApiManager: OdpSegmentApiManager; @@ -80,8 +80,8 @@ describe('OdpManager', () => { fakeLogger = instance(mockLogger); fakeRequestHandler = instance(mockRequestHandler); - mockEventApiManager = mock(); - mockEventManager = mock(); + mockEventApiManager = mock(); + mockEventManager = mock(); mockSegmentApiManager = mock(); mockSegmentManager = mock(); mockBrowserOdpManager = mock(); @@ -236,34 +236,35 @@ describe('OdpManager', () => { expect(browserOdpManagerB.eventManager).not.toBe(null); }); - it("should call event manager's sendEvent if ODP Event is valid", async () => { - const browserOdpManager = new BrowserOdpManager({ - odpOptions: { - eventManager: fakeEventManager, - }, - }); + // it("should call event manager's sendEvent if ODP Event is valid", async () => { + // const browserOdpManager = new BrowserOdpManager({ + // odpOptions: { + // eventManager: fakeEventManager, + // }, + // }); - const odpConfig = new OdpConfig('key', 'host', []); + // const odpConfig = new OdpConfig('key', 'host', []); - browserOdpManager.updateSettings(odpConfig); + // browserOdpManager.updateSettings(odpConfig); - // Test Valid OdpEvent - calls event manager with valid OdpEvent object - const validIdentifiers = new Map(); - validIdentifiers.set('vuid', vuidA); + // // Test Valid OdpEvent - calls event manager with valid OdpEvent object + // const validIdentifiers = new Map(); + // validIdentifiers.set('vuid', vuidA); - const validOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, validIdentifiers); + // const validOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, validIdentifiers); - await browserOdpManager.sendEvent(validOdpEvent); - verify(mockEventManager.sendEvent(anything())).once(); + // await browserOdpManager.sendEvent(validOdpEvent); + // verify(mockEventManager.sendEvent(anything())).once(); - // Test Invalid OdpEvents - logs error and short circuits - // Does not include `vuid` in identifiers does not have a local this.vuid populated in BrowserOdpManager - browserOdpManager.vuid = undefined; - const invalidOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, undefined); + // // Test Invalid OdpEvents - logs error and short circuits + // // Does not include `vuid` in identifiers does not have a local this.vuid populated in BrowserOdpManager + // browserOdpManager.vuid = undefined; + // const invalidOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, undefined); - await expect(browserOdpManager.sendEvent(invalidOdpEvent)) - .rejects.toThrow(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING) - }); + // await expect(browserOdpManager.sendEvent(invalidOdpEvent)).rejects.toThrow( + // ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING + // ); + // }); describe('Populates BrowserOdpManager correctly with all odpOptions', () => { it('odpOptions.disabled = true disables BrowserOdpManager', () => { @@ -439,8 +440,7 @@ describe('OdpManager', () => { }); it('Browser default Events API Request Handler timeout should be used when odpOptions does not include eventsApiTimeout', () => { - const odpOptions: OdpOptions = { - }; + const odpOptions: OdpOptions = {}; const browserOdpManager = new BrowserOdpManager({ odpOptions, @@ -450,7 +450,7 @@ describe('OdpManager', () => { expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(10000); }); - it('Custom odpOptions.eventFlushInterval overrides default Event Manager flush interval', () => { + it('Custom odpOptions.eventFlushInterval cannot override the default Event Manager flush interval', () => { const odpOptions: OdpOptions = { eventFlushInterval: 4000, }; @@ -460,7 +460,7 @@ describe('OdpManager', () => { }); // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(4000); + expect(browserOdpManager.eventManager.flushInterval).toBe(0); // Note: Browser flush interval is always 0 due to use of Pixel API }); it('Default ODP event flush interval is used when odpOptions does not include eventFlushInterval', () => { @@ -471,7 +471,7 @@ describe('OdpManager', () => { }); // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(1000); + expect(browserOdpManager.eventManager.flushInterval).toBe(0); }); it('ODP event batch size set to one when odpOptions.eventFlushInterval set to 0', () => { @@ -490,7 +490,7 @@ describe('OdpManager', () => { expect(browserOdpManager.eventManager.batchSize).toBe(1); }); - it('Custom odpOptions.eventBatchSize overrides default Event Manager batch size', () => { + it('Custom odpOptions.eventBatchSize does not override default Event Manager batch size', () => { const odpOptions: OdpOptions = { eventBatchSize: 2, }; @@ -500,7 +500,7 @@ describe('OdpManager', () => { }); // @ts-ignore - expect(browserOdpManager.eventManager.batchSize).toBe(2); + expect(browserOdpManager.eventManager.batchSize).toBe(1); // Note: Browser event batch size is always 1 due to use of Pixel API }); it('Custom odpOptions.eventQueueSize overrides default Event Manager queue size', () => { @@ -550,7 +550,7 @@ describe('OdpManager', () => { const fakeClientEngine = 'test-javascript-sdk'; const fakeClientVersion = '1.2.3'; - const customEventManager = new OdpEventManager({ + const customEventManager = new BrowserOdpEventManager({ odpConfig, apiManager: fakeEventApiManager, logger: fakeLogger, @@ -580,9 +580,9 @@ describe('OdpManager', () => { const fakeClientEngine = 'test-javascript-sdk'; const fakeClientVersion = '1.2.3'; - const customEventManager = new OdpEventManager({ + const customEventManager = new BrowserOdpEventManager({ odpConfig, - apiManager: new OdpEventApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger), + apiManager: new BrowserOdpEventApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger), logger: fakeLogger, clientEngine: fakeClientEngine, clientVersion: fakeClientVersion, @@ -617,7 +617,7 @@ describe('OdpManager', () => { expect(browserOdpManager.eventManager.batchSize).toBe(1); // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(1); + expect(browserOdpManager.eventManager.flushInterval).toBe(0); // Note: Browser event flush interval will always be 0 due to use of Pixel API // @ts-ignore expect(browserOdpManager.eventManager.queueSize).toBe(1); @@ -652,10 +652,10 @@ describe('OdpManager', () => { expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4); // @ts-ignore - expect(browserOdpManager.eventManager.batchSize).toBe(4); + expect(browserOdpManager.eventManager.batchSize).toBe(1); // Note: Browser batch size will always be 1 due to use of Pixel API // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(4); + expect(browserOdpManager.eventManager.flushInterval).toBe(0); // Note: Browser event flush interval will always be 0 due to use of Pixel API // @ts-ignore expect(browserOdpManager.eventManager.queueSize).toBe(4); diff --git a/packages/optimizely-sdk/tests/odpManager.spec.ts b/packages/optimizely-sdk/tests/odpManager.spec.ts index 9fd12605d..87009135e 100644 --- a/packages/optimizely-sdk/tests/odpManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.spec.ts @@ -1,4 +1,3 @@ -import { LRUCache } from './../lib/utils/lru_cache/lru_cache'; /** * Copyright 2023, Optimizely * @@ -25,10 +24,10 @@ import { LogHandler, LogLevel } from '../lib/modules/logging'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; -import { OdpManager } from './../lib/core/odp/odp_manager'; +import { NodeOdpManager as OdpManager } from './../lib/plugins/odp_manager/index.node'; import { OdpConfig } from '../lib/core/odp/odp_config'; -import { OdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; -import { OdpEventManager } from '../lib/core/odp/odp_event_manager'; +import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; +import { NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { ServerLRUCache } from '../lib/utils/lru_cache'; @@ -90,23 +89,21 @@ describe('OdpManager', () => { const odpManagerInstance = (config?: OdpConfig) => new OdpManager({ - segmentLRUCache: new ServerLRUCache(), - segmentRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, odpOptions: { eventManager, segmentManager, + segmentsRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, }, }); it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { const odpManager = new OdpManager({ - segmentLRUCache: new ServerLRUCache(), - segmentRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, logger, odpOptions: { disabled: true, + segmentsRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, }, }); verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); @@ -140,10 +137,9 @@ describe('OdpManager', () => { it('should use new settings in event manager when ODP Config is updated', async () => { const odpManager = new OdpManager({ - segmentLRUCache: new ServerLRUCache(), - segmentRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, odpOptions: { + segmentsRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, eventManager: new OdpEventManager({ odpConfig, apiManager: eventApiManager, @@ -176,11 +172,10 @@ describe('OdpManager', () => { it('should use new settings in segment manager when ODP Config is updated', async () => { const odpManager = new OdpManager({ - segmentLRUCache: new ServerLRUCache(), - segmentRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, odpOptions: { segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), + segmentsRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, }, }); @@ -205,10 +200,11 @@ describe('OdpManager', () => { expect(odpManagerA.eventManager).not.toBe(null); const odpManagerB = new OdpManager({ - segmentLRUCache: new ServerLRUCache(), - segmentRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, logger, + odpOptions: { + segmentsRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, + }, }); expect(odpManagerB.eventManager).not.toBe(null); }); @@ -218,9 +214,10 @@ describe('OdpManager', () => { expect(odpManagerA.segmentManager).not.toBe(null); const odpManagerB = new OdpManager({ - segmentLRUCache: new ServerLRUCache(), - segmentRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, + odpOptions: { + segmentsRequestHandler: defaultRequestHandler, + eventRequestHandler: defaultRequestHandler, + }, }); expect(odpManagerB.eventManager).not.toBe(null); }); From 43cb6ea8397272cd65453367918dbc3f37b050a5 Mon Sep 17 00:00:00 2001 From: Jae Kim <45045038+jaeopt@users.noreply.github.com> Date: Wed, 3 May 2023 11:15:16 -0700 Subject: [PATCH 011/200] [FSSDK-9126] fix(ats): handle INVALID_IDENTIFIER_EXCEPTION error (#821) --- .../lib/core/odp/odp_segment_api_manager.ts | 8 ++++-- .../optimizely-sdk/lib/core/odp/odp_types.ts | 1 + .../tests/odpSegmentApiManager.spec.ts | 28 ++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts index fd7221717..60835db4e 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts @@ -108,9 +108,13 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { } if (parsedSegments.errors?.length > 0) { - const errors = parsedSegments.errors.map(e => e.message).join('; '); + const { code, classification } = parsedSegments.errors[0].extensions; - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (${errors})`); + if (code == "INVALID_IDENTIFIER_EXCEPTION") { + this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (invalid identifier)`); + } else { + this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (${classification})`); + } return null; } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_types.ts b/packages/optimizely-sdk/lib/core/odp/odp_types.ts index 3fed03408..bd3e8217e 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_types.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_types.ts @@ -58,6 +58,7 @@ export interface Location { * Extended error information */ export interface Extension { + code: string; classification: string; } diff --git a/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts b/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts index a0326df6e..2d155e73f 100644 --- a/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts @@ -179,6 +179,32 @@ describe('OdpSegmentApiManager', () => { }); it('should handle error with invalid identifier', async () => { + const INVALID_USER_ID = 'invalid-user'; + const errorJsonResponse = + '{"errors":[{"message":' + + '"Exception while fetching data (/customer) : ' + + `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + + '"locations":[{"line":1,"column":8}],"path":["customer"],' + + '"extensions":{"code": "INVALID_IDENTIFIER_EXCEPTION","classification":"DataFetchingException"}}],' + + '"data":{"customer":null}}'; + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( + abortableRequest(200, errorJsonResponse) + ); + const manager = managerInstance(); + + const segments = await manager.fetchSegments( + API_key, + GRAPHQL_ENDPOINT, + USER_KEY, + INVALID_USER_ID, + SEGMENTS_TO_CHECK + ); + + expect(segments).toBeNull(); + verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (invalid identifier)')).once(); + }); + + it('should handle other fetch error responses', async () => { const INVALID_USER_ID = 'invalid-user'; const errorJsonResponse = '{"errors":[{"message":' + @@ -201,7 +227,7 @@ describe('OdpSegmentApiManager', () => { ); expect(segments).toBeNull(); - verify(mockLogger.log(anything(), anyString())).once(); + verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (DataFetchingException)')).once(); }); it('should handle unrecognized JSON responses', async () => { From 34a7bf00b605128ad2f8aead35f432e07d081e9a Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Wed, 3 May 2023 16:47:34 -0400 Subject: [PATCH 012/200] [FSSDK-9152] Fix "client_initialized" event not firing (#820) --- .../lib/core/odp/odp_event_manager.ts | 5 --- .../optimizely-sdk/lib/index.browser.tests.js | 34 +++++++++++++++++++ .../tests/odpEventManager.spec.ts | 3 +- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index ed1457d0a..1641e6f9b 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -247,11 +247,6 @@ export abstract class OdpEventManager implements IOdpEventManager { return; } - if (!this.odpConfig.isReady()) { - this.logger.log(LogLevel.DEBUG, 'Unable to Process ODP Event. ODPConfig is not ready.'); - return; - } - if (this.queue.length >= this.queueSize) { this.logger.log( LogLevel.WARNING, diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index eb4cc9e2d..c6f2f4d51 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -903,6 +903,40 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.notCalled(logger.error); }); + + it('should send odp client_initialized on client instantiation', async () => { + const odpConfig = new OdpConfig(); + const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); + sinon.spy(apiManager, 'sendEvents'); + const eventManager = new BrowserOdpEventManager({ + odpConfig, + apiManager, + logger, + }); + const datafile = testData.getOdpIntegratedConfigWithSegments(); + const client = optimizelyFactory.createInstance({ + datafile, + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpOptions: { + odpConfig, + eventManager, + }, + }); + + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + + clock.tick(100); + + const [,,events] = apiManager.sendEvents.getCall(0).args; + const [firstEvent] = events; + assert.equal(firstEvent.action, 'client_initialized'); + assert.equal(firstEvent.type, 'fullstack'); + }); }); }); }); diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index 7321cfc47..992757602 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -168,7 +168,8 @@ describe('OdpEventManager', () => { eventManager.sendEvent(EVENTS[0]); - verify(mockLogger.log(LogLevel.DEBUG, 'Unable to Process ODP Event. ODPConfig is not ready.')).once(); + // In a Node context, the events should be discarded + verify(mockLogger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.')).once(); }); it('should discard events with invalid data', () => { From 082c086e1aba3ace76d3944c0948da3128ddeccc Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Wed, 3 May 2023 17:14:42 -0400 Subject: [PATCH 013/200] fix(ats): [FSSDK-9114] Various Fixes for FSC (#818) Note: Still missing fix for event dispatch issue in FSC. Will resolve in a later patch. --- .../lib/core/odp/odp_event_manager.ts | 16 +- .../lib/core/odp/odp_manager.ts | 52 +- .../lib/core/odp/odp_segment_manager.ts | 36 +- .../lib/core/odp/optimizely_segment_option.ts | 4 +- .../lib/core/project_config/index.tests.js | 345 +++-- .../lib/core/project_config/index.ts | 22 +- .../project_config/project_config_manager.ts | 30 +- packages/optimizely-sdk/lib/export_types.ts | 1 + .../optimizely-sdk/lib/index.browser.tests.js | 152 ++- .../lib/optimizely/index.tests.js | 35 +- .../optimizely-sdk/lib/optimizely/index.ts | 41 +- .../optimizely_user_context/index.tests.js | 30 +- .../lib/optimizely_user_context/index.ts | 23 +- .../odp/event_manager/index.browser.ts | 14 +- .../plugins/odp/event_manager/index.node.ts | 17 +- packages/optimizely-sdk/lib/shared_types.ts | 34 +- .../optimizely-sdk/lib/tests/test_data.js | 1154 +++++++++-------- .../optimizely-sdk/lib/utils/enums/index.ts | 12 +- .../lib/utils/lru_cache/browser_lru_cache.ts | 4 +- .../lib/utils/lru_cache/index.ts | 4 +- .../lib/utils/lru_cache/lru_cache.ts | 10 +- .../lib/utils/lru_cache/server_lru_cache.ts | 4 +- .../tests/odpSegmentManager.spec.ts | 33 +- 23 files changed, 1201 insertions(+), 872 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 1641e6f9b..8f90f73d0 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -21,14 +21,10 @@ import { ERROR_MESSAGES, ODP_USER_KEY, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION import { OdpEvent } from './odp_event'; import { OdpConfig } from './odp_config'; -import { OdpEventApiManager } from './odp_event_api_manager'; +import { IOdpEventApiManager } from './odp_event_api_manager'; import { invalidOdpDataFound } from './odp_utils'; const MAX_RETRIES = 3; -const DEFAULT_BATCH_SIZE = 10; -const DEFAULT_FLUSH_INTERVAL_MSECS = 1000; -const DEFAULT_BROWSER_QUEUE_SIZE = 100; -const DEFAULT_SERVER_QUEUE_SIZE = 10000; /** * Event dispatcher's execution states @@ -51,7 +47,7 @@ export interface IOdpEventManager { registerVuid(vuid: string): void; - identifyUser(userId: string, vuid?: string): void; + identifyUser(userId?: string, vuid?: string): void; sendEvent(event: OdpEvent): void; @@ -85,7 +81,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * REST API Manager used to send the events * @private */ - private readonly apiManager: OdpEventApiManager; + private readonly apiManager: IOdpEventApiManager; /** * Handler for recording execution logs * @private @@ -100,7 +96,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * Maximum number of events to process at once. Ignored in browser context * @protected */ - protected batchSize!: number; + protected batchSize!: number; /** * Milliseconds between setTimeout() to process new batches. Ignored in browser context * @protected @@ -128,7 +124,7 @@ export abstract class OdpEventManager implements IOdpEventManager { flushInterval, }: { odpConfig: OdpConfig; - apiManager: OdpEventApiManager; + apiManager: IOdpEventApiManager; logger: LogHandler; clientEngine: string; clientVersion: string; @@ -148,7 +144,7 @@ export abstract class OdpEventManager implements IOdpEventManager { protected abstract initParams( batchSize: number | undefined, queueSize: number | undefined, - flushInterval: number | undefined, + flushInterval: number | undefined ): void; /** diff --git a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts index 8420613d2..961a86796 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts @@ -21,16 +21,29 @@ import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; import { VuidManager } from '../../plugins/vuid_manager'; import { OdpConfig } from './odp_config'; -import { OdpEventManager } from './odp_event_manager'; -import { OdpSegmentManager } from './odp_segment_manager'; +import { IOdpEventManager } from './odp_event_manager'; +import { IOdpSegmentManager } from './odp_segment_manager'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { invalidOdpDataFound } from './odp_utils'; import { OdpEvent } from './odp_event'; +export interface IOdpManager { + enabled: boolean; + segmentManager: IOdpSegmentManager | undefined; + eventManager: IOdpEventManager | undefined; + updateSettings({ apiKey, apiHost, segmentsToCheck }: OdpConfig): boolean; + close(): void; + fetchQualifiedSegments(userId: string, options?: Array): Promise; + identifyUser(userId?: string, vuid?: string): void; + sendEvent({ type, action, identifiers, data }: OdpEvent): void; + isVuidEnabled(): boolean; + getVuid(): string | undefined; +} + /** * Orchestrates segments manager, event manager, and ODP configuration */ -export abstract class OdpManager { +export abstract class OdpManager implements IOdpManager { initPromise?: Promise; enabled = true; logger: LogHandler = getLogger(); @@ -40,20 +53,20 @@ export abstract class OdpManager { * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. * It fetches all qualified segments for the given user context and manages the segments cache for all user contexts. */ - public segmentManager: OdpSegmentManager | undefined; + segmentManager: IOdpSegmentManager | undefined; /** * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. */ - public eventManager: OdpEventManager | undefined; + eventManager: IOdpEventManager | undefined; constructor() {} /** * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments */ - public updateSettings({ apiKey, apiHost, segmentsToCheck }: OdpConfig): boolean { + updateSettings({ apiKey, apiHost, segmentsToCheck }: OdpConfig): boolean { if (!this.enabled) { return false; } @@ -85,7 +98,7 @@ export abstract class OdpManager { /** * Attempts to stop the current instance of ODP Manager's event manager, if it exists and is running. */ - public close(): void { + close(): void { if (!this.enabled) { return; } @@ -100,10 +113,7 @@ export abstract class OdpManager { * @param {Array} options - An array of OptimizelySegmentOption used to ignore and/or reset the cache. * @returns {Promise} A promise holding either a list of qualified segments or null. */ - public async fetchQualifiedSegments( - userId: string, - options: Array = [] - ): Promise { + async fetchQualifiedSegments(userId: string, options: Array = []): Promise { if (!this.enabled) { this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED); return null; @@ -127,7 +137,7 @@ export abstract class OdpManager { * @param {string} vuid (Optional) Secondary unique identifier of a target user, primarily used by client SDKs. * @returns */ - public identifyUser(userId?: string, vuid?: string): void { + identifyUser(userId?: string, vuid?: string): void { if (!this.enabled) { this.logger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED); return; @@ -155,7 +165,13 @@ export abstract class OdpManager { * Sends an event to the ODP Server via the ODP Events API * @param {OdpEvent} > ODP Event to send to event manager */ - public sendEvent({ type, action, identifiers, data }: OdpEvent): void { + sendEvent({ type, action, identifiers, data }: OdpEvent): void { + let mType = type; + + if (typeof mType !== 'string' || mType === '') { + mType = 'fullstack'; + } + if (!this.enabled) { throw new Error(ERROR_MESSAGES.ODP_NOT_ENABLED); } @@ -172,10 +188,14 @@ export abstract class OdpManager { throw new Error(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING); } - this.eventManager.sendEvent(new OdpEvent(type, action, identifiers, data)); + if (typeof action !== 'string' || action === '') { + throw new Error('ODP action is not valid (cannot be empty).'); + } + + this.eventManager.sendEvent(new OdpEvent(mType, action, identifiers, data)); } - public abstract isVuidEnabled(): boolean; + abstract isVuidEnabled(): boolean; - public abstract getVuid(): string | undefined; + abstract getVuid(): string | undefined; } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts index 8a4deb283..a8c30d616 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts @@ -16,15 +16,26 @@ import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; -import { LRUCache } from '../../utils/lru_cache'; -import { OdpSegmentApiManager } from './odp_segment_api_manager'; +import { ICache } from '../../utils/lru_cache'; +import { IOdpSegmentApiManager } from './odp_segment_api_manager'; import { OdpConfig } from './odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; +export interface IOdpSegmentManager { + fetchQualifiedSegments( + userKey: ODP_USER_KEY, + userValue: string, + options: Array + ): Promise; + reset(): void; + makeCacheKey(userKey: string, userValue: string): string; + updateSettings(config: OdpConfig): void; +} + /** * Schedules connections to ODP for audience segmentation and caches the results. */ -export class OdpSegmentManager { +export class OdpSegmentManager implements IOdpSegmentManager { /** * ODP configuration settings in used * @private @@ -35,13 +46,13 @@ export class OdpSegmentManager { * Holds cached audience segments * @private */ - private _segmentsCache: LRUCache; + private _segmentsCache: ICache; /** * Getter for private segments cache * @public */ - public get segmentsCache(): LRUCache { + public get segmentsCache(): ICache { return this._segmentsCache; } @@ -49,7 +60,7 @@ export class OdpSegmentManager { * GraphQL API Manager used to fetch segments * @private */ - private odpSegmentApiManager: OdpSegmentApiManager; + private odpSegmentApiManager: IOdpSegmentApiManager; /** * Handler for recording execution logs @@ -59,8 +70,8 @@ export class OdpSegmentManager { constructor( odpConfig: OdpConfig, - segmentsCache: LRUCache, - odpSegmentApiManager: OdpSegmentApiManager, + segmentsCache: ICache, + odpSegmentApiManager: IOdpSegmentApiManager, logger?: LogHandler ) { this.odpConfig = odpConfig; @@ -100,7 +111,9 @@ export class OdpSegmentManager { const ignoreCache = options.includes(OptimizelySegmentOption.IGNORE_CACHE); const resetCache = options.includes(OptimizelySegmentOption.RESET_CACHE); - if (resetCache) this.reset(); + if (resetCache) { + this.reset(); + } if (!ignoreCache && !resetCache) { const cachedSegments = this._segmentsCache.lookup(cacheKey); @@ -121,7 +134,9 @@ export class OdpSegmentManager { segmentsToCheck ); - if (segments && !ignoreCache) this._segmentsCache.save({ key: cacheKey, value: segments }); + if (segments && !ignoreCache) { + this._segmentsCache.save({ key: cacheKey, value: segments }); + } return segments; } @@ -149,5 +164,6 @@ export class OdpSegmentManager { */ public updateSettings(config: OdpConfig): void { this.odpConfig = config; + this._segmentsCache.reset(); } } diff --git a/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts b/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts index e9a7d0712..112cd39cc 100644 --- a/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts +++ b/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts @@ -16,6 +16,6 @@ // Options for defining behavior of OdpSegmentManager's caching mechanism when calling fetchSegments() export enum OptimizelySegmentOption { - IGNORE_CACHE, - RESET_CACHE, + IGNORE_CACHE = 'IGNORE_CACHE', + RESET_CACHE = 'RESET_CACHE', } diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/packages/optimizely-sdk/lib/core/project_config/index.tests.js index fa4c03d16..6210f03f6 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -21,11 +21,7 @@ import { getLogger } from '../../modules/logging'; import fns from '../../utils/fns'; import projectConfig from './'; -import { - ERROR_MESSAGES, - FEATURE_VARIABLE_TYPES, - LOG_LEVEL, -} from '../../utils/enums'; +import { ERROR_MESSAGES, FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../../utils/enums'; import * as loggerPlugin from '../../plugins/logger'; import testDatafile from '../../tests/test_data'; import configValidator from '../../utils/config_validator'; @@ -33,13 +29,13 @@ import configValidator from '../../utils/config_validator'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var logger = getLogger(); -describe('lib/core/project_config', function () { - describe('createProjectConfig method', function () { - it('should set properties correctly when createProjectConfig is called', function () { +describe('lib/core/project_config', function() { + describe('createProjectConfig method', function() { + it('should set properties correctly when createProjectConfig is called', function() { var testData = testDatafile.getTestProjectConfig(); var configObj = projectConfig.createProjectConfig(testData); - forEach(testData.audiences, function (audience) { + forEach(testData.audiences, function(audience) { audience.conditions = JSON.parse(audience.conditions); }); @@ -48,8 +44,8 @@ describe('lib/core/project_config', function () { assert.strictEqual(configObj.revision, testData.revision); assert.deepEqual(configObj.events, testData.events); assert.deepEqual(configObj.audiences, testData.audiences); - testData.groups.forEach(function (group) { - group.experiments.forEach(function (experiment) { + testData.groups.forEach(function(group) { + group.experiments.forEach(function(experiment) { experiment.groupId = group.id; experiment.variationKeyMap = fns.keyBy(experiment.variations, 'key'); }); @@ -64,14 +60,14 @@ describe('lib/core/project_config', function () { assert.deepEqual(configObj.groupIdMap, expectedGroupIdMap); var expectedExperiments = testData.experiments; - forEach(configObj.groupIdMap, function (group, Id) { - forEach(group.experiments, function (experiment) { + forEach(configObj.groupIdMap, function(group, Id) { + forEach(group.experiments, function(experiment) { experiment.groupId = Id; expectedExperiments.push(experiment); }); }); - forEach(expectedExperiments, function (experiment) { + forEach(expectedExperiments, function(experiment) { experiment.variationKeyMap = fns.keyBy(experiment.variations, 'key'); }); @@ -174,35 +170,35 @@ describe('lib/core/project_config', function () { }; }); - it('should not mutate the datafile', function () { + it('should not mutate the datafile', function() { var datafile = testDatafile.getTypedAudiencesConfig(); var datafileClone = cloneDeep(datafile); projectConfig.createProjectConfig(datafile); assert.deepEqual(datafileClone, datafile); }); - describe('feature management', function () { + describe('feature management', function() { var configObj; - beforeEach(function () { + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); }); - it('creates a rolloutIdMap from rollouts in the datafile', function () { + it('creates a rolloutIdMap from rollouts in the datafile', function() { assert.deepEqual(configObj.rolloutIdMap, testDatafile.datafileWithFeaturesExpectedData.rolloutIdMap); }); - it('creates a variationVariableUsageMap from rollouts and experiments with features in the datafile', function () { + it('creates a variationVariableUsageMap from rollouts and experiments with features in the datafile', function() { assert.deepEqual( configObj.variationVariableUsageMap, testDatafile.datafileWithFeaturesExpectedData.variationVariableUsageMap ); }); - it('creates a featureKeyMap from feature flags in the datafile', function () { + it('creates a featureKeyMap from feature flags in the datafile', function() { assert.deepEqual(configObj.featureKeyMap, testDatafile.datafileWithFeaturesExpectedData.featureKeyMap); }); - it('adds variations from rollout experiments to variationIdMap', function () { + it('adds variations from rollout experiments to variationIdMap', function() { assert.deepEqual(configObj.variationIdMap['594032'], { variables: [ { value: 'true', id: '4919852825313280' }, @@ -252,24 +248,24 @@ describe('lib/core/project_config', function () { }); }); - describe('flag variations', function () { + describe('flag variations', function() { var configObj; - beforeEach(function () { + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTestDecideProjectConfig()); }); - it('it should populate flagVariationsMap correctly', function () { + it('it should populate flagVariationsMap correctly', function() { var allVariationsForFlag = configObj.flagVariationsMap; var feature1Variations = allVariationsForFlag.feature_1; var feature2Variations = allVariationsForFlag.feature_2; var feature3Variations = allVariationsForFlag.feature_3; - var feature1VariationsKeys = feature1Variations.map((variation) => { + var feature1VariationsKeys = feature1Variations.map(variation => { return variation.key; }, {}); - var feature2VariationsKeys = feature2Variations.map((variation) => { + var feature2VariationsKeys = feature2Variations.map(variation => { return variation.key; }, {}); - var feature3VariationsKeys = feature3Variations.map((variation) => { + var feature3VariationsKeys = feature3Variations.map(variation => { return variation.key; }, {}); @@ -280,52 +276,52 @@ describe('lib/core/project_config', function () { }); }); - describe('projectConfig helper methods', function () { + describe('projectConfig helper methods', function() { var testData = cloneDeep(testDatafile.getTestProjectConfig()); var configObj; var createdLogger = loggerPlugin.createLogger({ logLevel: LOG_LEVEL.INFO }); - beforeEach(function () { + beforeEach(function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); sinon.stub(createdLogger, 'log'); }); - afterEach(function () { + afterEach(function() { createdLogger.log.restore(); }); - it('should retrieve experiment ID for valid experiment key in getExperimentId', function () { + it('should retrieve experiment ID for valid experiment key in getExperimentId', function() { assert.strictEqual( projectConfig.getExperimentId(configObj, testData.experiments[0].key), testData.experiments[0].id ); }); - it('should throw error for invalid experiment key in getExperimentId', function () { - assert.throws(function () { + it('should throw error for invalid experiment key in getExperimentId', function() { + assert.throws(function() { projectConfig.getExperimentId(configObj, 'invalidExperimentKey'); }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); }); - it('should retrieve layer ID for valid experiment key in getLayerId', function () { + it('should retrieve layer ID for valid experiment key in getLayerId', function() { assert.strictEqual(projectConfig.getLayerId(configObj, '111127'), '4'); }); - it('should throw error for invalid experiment key in getLayerId', function () { - assert.throws(function () { + it('should throw error for invalid experiment key in getLayerId', function() { + assert.throws(function() { projectConfig.getLayerId(configObj, 'invalidExperimentKey'); }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey')); }); - it('should retrieve attribute ID for valid attribute key in getAttributeId', function () { + it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { assert.strictEqual(projectConfig.getAttributeId(configObj, 'browser_type'), '111094'); }); - it('should retrieve attribute ID for reserved attribute key in getAttributeId', function () { + it('should retrieve attribute ID for reserved attribute key in getAttributeId', function() { assert.strictEqual(projectConfig.getAttributeId(configObj, '$opt_user_agent'), '$opt_user_agent'); }); - it('should return null for invalid attribute key in getAttributeId', function () { + it('should return null for invalid attribute key in getAttributeId', function() { assert.isNull(projectConfig.getAttributeId(configObj, 'invalidAttributeKey', createdLogger)); assert.strictEqual( buildLogMessageFromArgs(createdLogger.log.lastCall.args), @@ -333,7 +329,7 @@ describe('lib/core/project_config', function () { ); }); - it('should return null for invalid attribute key in getAttributeId', function () { + it('should return null for invalid attribute key in getAttributeId', function() { // Adding attribute in key map with reserved prefix configObj.attributeKeyMap['$opt_some_reserved_attribute'] = { id: '42', @@ -346,65 +342,65 @@ describe('lib/core/project_config', function () { ); }); - it('should retrieve event ID for valid event key in getEventId', function () { + it('should retrieve event ID for valid event key in getEventId', function() { assert.strictEqual(projectConfig.getEventId(configObj, 'testEvent'), '111095'); }); - it('should return null for invalid event key in getEventId', function () { + it('should return null for invalid event key in getEventId', function() { assert.isNull(projectConfig.getEventId(configObj, 'invalidEventKey')); }); - it('should retrieve experiment status for valid experiment key in getExperimentStatus', function () { + it('should retrieve experiment status for valid experiment key in getExperimentStatus', function() { assert.strictEqual( projectConfig.getExperimentStatus(configObj, testData.experiments[0].key), testData.experiments[0].status ); }); - it('should throw error for invalid experiment key in getExperimentStatus', function () { - assert.throws(function () { + it('should throw error for invalid experiment key in getExperimentStatus', function() { + assert.throws(function() { projectConfig.getExperimentStatus(configObj, 'invalidExperimentKey'); }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); }); - it('should return true if experiment status is set to Running in isActive', function () { + it('should return true if experiment status is set to Running in isActive', function() { assert.isTrue(projectConfig.isActive(configObj, 'testExperiment')); }); - it('should return false if experiment status is not set to Running in isActive', function () { + it('should return false if experiment status is not set to Running in isActive', function() { assert.isFalse(projectConfig.isActive(configObj, 'testExperimentNotRunning')); }); - it('should return true if experiment status is set to Running in isRunning', function () { + it('should return true if experiment status is set to Running in isRunning', function() { assert.isTrue(projectConfig.isRunning(configObj, 'testExperiment')); }); - it('should return false if experiment status is not set to Running in isRunning', function () { + it('should return false if experiment status is not set to Running in isRunning', function() { assert.isFalse(projectConfig.isRunning(configObj, 'testExperimentLaunched')); }); - it('should retrieve variation key for valid experiment key and variation ID in getVariationKeyFromId', function () { + it('should retrieve variation key for valid experiment key and variation ID in getVariationKeyFromId', function() { assert.deepEqual( projectConfig.getVariationKeyFromId(configObj, testData.experiments[0].variations[0].id), testData.experiments[0].variations[0].key ); }); - it('should retrieve traffic allocation given valid experiment key in getTrafficAllocation', function () { + it('should retrieve traffic allocation given valid experiment key in getTrafficAllocation', function() { assert.deepEqual( projectConfig.getTrafficAllocation(configObj, testData.experiments[0].id), testData.experiments[0].trafficAllocation ); }); - it('should throw error for invalid experient key in getTrafficAllocation', function () { - assert.throws(function () { + it('should throw error for invalid experient key in getTrafficAllocation', function() { + assert.throws(function() { projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); }); - describe('#getVariationIdFromExperimentAndVariationKey', function () { - it('should return the variation id for the given experiment key and variation key', function () { + describe('#getVariationIdFromExperimentAndVariationKey', function() { + it('should return the variation id for the given experiment key and variation key', function() { assert.strictEqual( projectConfig.getVariationIdFromExperimentAndVariationKey( configObj, @@ -416,45 +412,36 @@ describe('lib/core/project_config', function () { }); }); - describe('#getSendFlagDecisionsValue', function () { - it('should return false when sendFlagDecisions is undefined', function () { + describe('#getSendFlagDecisionsValue', function() { + it('should return false when sendFlagDecisions is undefined', function() { configObj.sendFlagDecisions = undefined; - assert.deepEqual( - projectConfig.getSendFlagDecisionsValue(configObj), - false - ); + assert.deepEqual(projectConfig.getSendFlagDecisionsValue(configObj), false); }); - it('should return false when sendFlagDecisions is set to false', function () { + it('should return false when sendFlagDecisions is set to false', function() { configObj.sendFlagDecisions = false; - assert.deepEqual( - projectConfig.getSendFlagDecisionsValue(configObj), - false - ); + assert.deepEqual(projectConfig.getSendFlagDecisionsValue(configObj), false); }); - it('should return true when sendFlagDecisions is set to true', function () { + it('should return true when sendFlagDecisions is set to true', function() { configObj.sendFlagDecisions = true; - assert.deepEqual( - projectConfig.getSendFlagDecisionsValue(configObj), - true - ); + assert.deepEqual(projectConfig.getSendFlagDecisionsValue(configObj), true); }); }); - describe('feature management', function () { + describe('feature management', function() { var featureManagementLogger = loggerPlugin.createLogger({ logLevel: LOG_LEVEL.INFO }); - beforeEach(function () { + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); sinon.stub(featureManagementLogger, 'log'); }); - afterEach(function () { + afterEach(function() { featureManagementLogger.log.restore(); }); - describe('getVariableForFeature', function () { - it('should return a variable object for a valid variable and feature key', function () { + describe('getVariableForFeature', function() { + it('should return a variable object for a valid variable and feature key', function() { var featureKey = 'test_feature_for_experiment'; var variableKey = 'num_buttons'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); @@ -466,7 +453,7 @@ describe('lib/core/project_config', function () { }); }); - it('should return null for an invalid variable key and a valid feature key', function () { + it('should return null for an invalid variable key and a valid feature key', function() { var featureKey = 'test_feature_for_experiment'; var variableKey = 'notARealVariable____'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); @@ -478,7 +465,7 @@ describe('lib/core/project_config', function () { ); }); - it('should return null for an invalid feature key', function () { + it('should return null for an invalid feature key', function() { var featureKey = 'notARealFeature_____'; var variableKey = 'num_buttons'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); @@ -490,7 +477,7 @@ describe('lib/core/project_config', function () { ); }); - it('should return null for an invalid variable key and an invalid feature key', function () { + it('should return null for an invalid variable key and an invalid feature key', function() { var featureKey = 'notARealFeature_____'; var variableKey = 'notARealVariable____'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); @@ -503,8 +490,8 @@ describe('lib/core/project_config', function () { }); }); - describe('getVariableValueForVariation', function () { - it('returns a value for a valid variation and variable', function () { + describe('getVariableValueForVariation', function() { + it('returns a value for a valid variation and variable', function() { var variation = configObj.variationIdMap['594096']; var variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; var result = projectConfig.getVariableValueForVariation( @@ -528,7 +515,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, '20.25'); }); - it('returns null for a null variation', function () { + it('returns null for a null variation', function() { var variation = null; var variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; var result = projectConfig.getVariableValueForVariation( @@ -540,7 +527,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null for a null variable', function () { + it('returns null for a null variable', function() { var variation = configObj.variationIdMap['594096']; var variable = null; var result = projectConfig.getVariableValueForVariation( @@ -552,7 +539,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null for a null variation and null variable', function () { + it('returns null for a null variation and null variable', function() { var variation = null; var variable = null; var result = projectConfig.getVariableValueForVariation( @@ -564,7 +551,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null for a variation whose id is not in the datafile', function () { + it('returns null for a variation whose id is not in the datafile', function() { var variation = { key: 'some_variation', id: '999999999999', @@ -580,7 +567,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null if the variation does not have a value for this variable', function () { + it('returns null if the variation does not have a value for this variable', function() { var variation = configObj.variationIdMap['595008']; // This variation has no variable values associated with it var variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; var result = projectConfig.getVariableValueForVariation( @@ -593,15 +580,15 @@ describe('lib/core/project_config', function () { }); }); - describe('getTypeCastValue', function () { - it('can cast a boolean', function () { + describe('getTypeCastValue', function() { + it('can cast a boolean', function() { var result = projectConfig.getTypeCastValue('true', FEATURE_VARIABLE_TYPES.BOOLEAN, featureManagementLogger); assert.strictEqual(result, true); result = projectConfig.getTypeCastValue('false', FEATURE_VARIABLE_TYPES.BOOLEAN, featureManagementLogger); assert.strictEqual(result, false); }); - it('can cast an integer', function () { + it('can cast an integer', function() { var result = projectConfig.getTypeCastValue('50', FEATURE_VARIABLE_TYPES.INTEGER, featureManagementLogger); assert.strictEqual(result, 50); var result = projectConfig.getTypeCastValue('-7', FEATURE_VARIABLE_TYPES.INTEGER, featureManagementLogger); @@ -610,7 +597,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, 0); }); - it('can cast a double', function () { + it('can cast a double', function() { var result = projectConfig.getTypeCastValue('89.99', FEATURE_VARIABLE_TYPES.DOUBLE, featureManagementLogger); assert.strictEqual(result, 89.99); var result = projectConfig.getTypeCastValue( @@ -625,7 +612,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, 10); }); - it('can return a string unmodified', function () { + it('can return a string unmodified', function() { var result = projectConfig.getTypeCastValue( 'message', FEATURE_VARIABLE_TYPES.STRING, @@ -634,7 +621,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, 'message'); }); - it('returns null and logs an error for an invalid boolean', function () { + it('returns null and logs an error for an invalid boolean', function() { var result = projectConfig.getTypeCastValue( 'notabool', FEATURE_VARIABLE_TYPES.BOOLEAN, @@ -647,7 +634,7 @@ describe('lib/core/project_config', function () { ); }); - it('returns null and logs an error for an invalid integer', function () { + it('returns null and logs an error for an invalid integer', function() { var result = projectConfig.getTypeCastValue( 'notanint', FEATURE_VARIABLE_TYPES.INTEGER, @@ -660,7 +647,7 @@ describe('lib/core/project_config', function () { ); }); - it('returns null and logs an error for an invalid double', function () { + it('returns null and logs an error for an invalid double', function() { var result = projectConfig.getTypeCastValue( 'notadouble', FEATURE_VARIABLE_TYPES.DOUBLE, @@ -675,32 +662,32 @@ describe('lib/core/project_config', function () { }); }); - describe('#getAudiencesById', function () { - beforeEach(function () { + describe('#getAudiencesById', function() { + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); }); - it('should retrieve audiences by checking first in typedAudiences, and then second in audiences', function () { + it('should retrieve audiences by checking first in typedAudiences, and then second in audiences', function() { assert.deepEqual(projectConfig.getAudiencesById(configObj), testDatafile.typedAudiencesById); }); }); - describe('#getExperimentAudienceConditions', function () { - it('should retrieve audiences for valid experiment key', function () { + describe('#getExperimentAudienceConditions', function() { + it('should retrieve audiences for valid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); assert.deepEqual(projectConfig.getExperimentAudienceConditions(configObj, testData.experiments[1].id), [ '11154', ]); }); - it('should throw error for invalid experiment key', function () { + it('should throw error for invalid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); - assert.throws(function () { + assert.throws(function() { projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); }); - it('should return experiment audienceIds if experiment has no audienceConditions', function () { + it('should return experiment audienceIds if experiment has no audienceConditions', function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); var result = projectConfig.getExperimentAudienceConditions(configObj, '11564051718'); assert.deepEqual(result, [ @@ -714,7 +701,7 @@ describe('lib/core/project_config', function () { ]); }); - it('should return experiment audienceConditions if experiment has audienceConditions', function () { + it('should return experiment audienceConditions if experiment has audienceConditions', function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); // audience_combinations_experiment has both audienceConditions and audienceIds // audienceConditions should be preferred over audienceIds @@ -727,20 +714,20 @@ describe('lib/core/project_config', function () { }); }); - describe('#isFeatureExperiment', function () { - it('returns true for a feature test', function () { + describe('#isFeatureExperiment', function() { + it('returns true for a feature test', function() { var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); var result = projectConfig.isFeatureExperiment(config, '594098'); // id of 'testing_my_feature' assert.isTrue(result); }); - it('returns false for an A/B test', function () { + it('returns false for an A/B test', function() { var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig()); var result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment' assert.isFalse(result); }); - it('returns true for a feature test in a mutex group', function () { + it('returns true for a feature test in a mutex group', function() { var config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig()); var result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1' assert.isTrue(result); @@ -749,62 +736,62 @@ describe('lib/core/project_config', function () { }); }); - describe('#getAudienceSegments', function () { - it('returns all qualified segments from an audience', function () { + describe('#getAudienceSegments', function() { + it('returns all qualified segments from an audience', function() { const dummyQualifiedAudienceJson = { - "id": "13389142234", - "conditions": [ - "and", + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }; const dummyQualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyQualifiedAudienceJson); assert.deepEqual(dummyQualifiedAudienceJsonSegments, ['odp-segment-1']); const dummyUnqualifiedAudienceJson = { - "id": "13389142234", - "conditions": [ - "and", + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "invalid" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'invalid', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }; const dummyUnqualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyUnqualifiedAudienceJson); assert.deepEqual(dummyUnqualifiedAudienceJsonSegments, []); }); - it('returns false for an A/B test', function () { + it('returns false for an A/B test', function() { var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig()); var result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment' assert.isFalse(result); }); - it('returns true for a feature test in a mutex group', function () { + it('returns true for a feature test in a mutex group', function() { var config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig()); var result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1' assert.isTrue(result); @@ -815,7 +802,6 @@ describe('lib/core/project_config', function () { }); describe('integrations', () => { - describe('#withSegments', () => { var config; beforeEach(() => { @@ -824,21 +810,21 @@ describe('lib/core/project_config', function () { it('should convert integrations from the datafile into the project config', () => { assert.exists(config.integrations); - assert.equal(config.integrations.length, 3); + assert.equal(config.integrations.length, 4); }); it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp) - }) + assert.exists(config.publicKeyForOdp); + }); it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp) - }) + assert.exists(config.hostForOdp); + }); it('should contain all expected unique odp segments in allSegments', () => { - assert.equal(config.allSegments.length, 3) - assert.deepEqual(config.allSegments, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']) - }) + assert.equal(config.allSegments.length, 3); + assert.deepEqual(config.allSegments, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']); + }); }); describe('#withoutSegments', () => { @@ -853,25 +839,34 @@ describe('lib/core/project_config', function () { }); it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp) - assert.equal(config.publicKeyForOdp, 'W4WzcEs-ABgXorzY7h1LCQ') - }) + assert.exists(config.publicKeyForOdp); + assert.equal(config.publicKeyForOdp, 'W4WzcEs-ABgXorzY7h1LCQ'); + }); it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp) - assert.equal(config.hostForOdp, 'https://api.zaius.com/') - }) + assert.exists(config.hostForOdp); + assert.equal(config.hostForOdp, 'https://api.zaius.com/'); + }); it('should contain all expected unique odp segments in all segments', () => { - assert.equal(config.allSegments.length, 0) - }) + assert.equal(config.allSegments.length, 0); + }); + }); + + describe('#withoutValidIntegrationKey', () => { + it('should throw an error when parsing the project config due to integrations not containing a key', () => { + const odpIntegratedConfigWithoutKey = testDatafile.getOdpIntegratedConfigWithoutKey(); + assert.throws(() => { + projectConfig.createProjectConfig(odpIntegratedConfigWithoutKey); + }); + }); }); describe('#withoutIntegrations', () => { var config; beforeEach(() => { - const odpIntegratedConfigWithSegments = testDatafile.getOdpIntegratedConfigWithSegments() - const noIntegrationsConfigWithSegments = { ...odpIntegratedConfigWithSegments, integrations: [] } + const odpIntegratedConfigWithSegments = testDatafile.getOdpIntegratedConfigWithSegments(); + const noIntegrationsConfigWithSegments = { ...odpIntegratedConfigWithSegments, integrations: [] }; config = projectConfig.createProjectConfig(noIntegrationsConfigWithSegments); }); @@ -879,13 +874,12 @@ describe('lib/core/project_config', function () { assert.equal(config.integrations.length, 0); }); }); - - }) + }); }); -describe('#tryCreatingProjectConfig', function () { +describe('#tryCreatingProjectConfig', function() { var stubJsonSchemaValidator; - beforeEach(function () { + beforeEach(function() { stubJsonSchemaValidator = { validate: sinon.stub().returns(true), }; @@ -893,25 +887,22 @@ describe('#tryCreatingProjectConfig', function () { sinon.spy(logger, 'error'); }); - afterEach(function () { + afterEach(function() { configValidator.validateDatafile.restore(); logger.error.restore(); }); - it('returns a project config object created by createProjectConfig when all validation is applied and there are no errors', function () { + it('returns a project config object created by createProjectConfig when all validation is applied and there are no errors', function() { var configDatafile = { foo: 'bar', - experiments: [ - { key: 'a' }, - { key: 'b' } - ] - } + experiments: [{ key: 'a' }, { key: 'b' }], + }; configValidator.validateDatafile.returns(configDatafile); var configObj = { foo: 'bar', experimentKeyMap: { - "a": { key: "a", variationKeyMap: {} }, - "b": { key: "b", variationKeyMap: {} } + a: { key: 'a', variationKeyMap: {} }, + b: { key: 'b', variationKeyMap: {} }, }, }; @@ -923,10 +914,10 @@ describe('#tryCreatingProjectConfig', function () { logger: logger, }); - assert.deepInclude(result.configObj, configObj) + assert.deepInclude(result.configObj, configObj); }); - it('returns an error when validateDatafile throws', function () { + it('returns an error when validateDatafile throws', function() { configValidator.validateDatafile.throws(); stubJsonSchemaValidator.validate.returns(true); var { error } = projectConfig.tryCreatingProjectConfig({ @@ -937,7 +928,7 @@ describe('#tryCreatingProjectConfig', function () { assert.isNotNull(error); }); - it('returns an error when jsonSchemaValidator.validate throws', function () { + it('returns an error when jsonSchemaValidator.validate throws', function() { configValidator.validateDatafile.returns(true); stubJsonSchemaValidator.validate.throws(); var { error } = projectConfig.tryCreatingProjectConfig({ @@ -948,15 +939,11 @@ describe('#tryCreatingProjectConfig', function () { assert.isNotNull(error); }); - it('skips json validation when jsonSchemaValidator is not provided', function () { - + it('skips json validation when jsonSchemaValidator is not provided', function() { var configDatafile = { foo: 'bar', - experiments: [ - { key: 'a' }, - { key: 'b' } - ] - } + experiments: [{ key: 'a' }, { key: 'b' }], + }; configValidator.validateDatafile.returns(configDatafile); diff --git a/packages/optimizely-sdk/lib/core/project_config/index.ts b/packages/optimizely-sdk/lib/core/project_config/index.ts index 5d3472c2b..a58c9cdc2 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.ts +++ b/packages/optimizely-sdk/lib/core/project_config/index.ts @@ -188,12 +188,22 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str if (projectConfig.integrations) { projectConfig.integrationKeyMap = keyBy(projectConfig.integrations, 'key'); - projectConfig.integrations - .filter(integration => integration.key === 'odp') - .forEach(integration => { - if (integration.publicKey) projectConfig.publicKeyForOdp = integration.publicKey; - if (integration.host) projectConfig.hostForOdp = integration.host; - }); + + projectConfig.integrations.forEach(integration => { + if (!('key' in integration)) { + throw new Error(sprintf(ERROR_MESSAGES.MISSING_INTEGRATION_KEY, MODULE_NAME)); + } + + if (integration.key === 'odp') { + if (integration.publicKey && !projectConfig.publicKeyForOdp) { + projectConfig.publicKeyForOdp = integration.publicKey; + } + + if (integration.host && !projectConfig.hostForOdp) { + projectConfig.hostForOdp = integration.host; + } + } + }); } projectConfig.experimentKeyMap = keyBy(projectConfig.experiments, 'key'); diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts index aea5951bd..21026679d 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts @@ -18,11 +18,7 @@ import { sprintf } from '../../utils/fns'; import { ERROR_MESSAGES } from '../../utils/enums'; import { createOptimizelyConfig } from '../optimizely_config'; -import { - OnReadyResult, - OptimizelyConfig, - DatafileManager, -} from '../../shared_types'; +import { OnReadyResult, OptimizelyConfig, DatafileManager } from '../../shared_types'; import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from '../project_config'; const logger = getLogger(); @@ -31,12 +27,12 @@ const MODULE_NAME = 'PROJECT_CONFIG_MANAGER'; interface ProjectConfigManagerConfig { // TODO[OASIS-6649]: Don't use object type // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, + datafile?: string | object; jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, + validate(jsonObject: unknown): boolean; }; - sdkKey?: string, - datafileManager?: DatafileManager + sdkKey?: string; + datafileManager?: DatafileManager; } /** @@ -74,7 +70,9 @@ export class ProjectConfigManager { this.jsonSchemaValidator = config.jsonSchemaValidator; if (!config.datafile && !config.sdkKey) { - const datafileAndSdkKeyMissingError = new Error(sprintf(ERROR_MESSAGES.DATAFILE_AND_SDK_KEY_MISSING, MODULE_NAME)); + const datafileAndSdkKeyMissingError = new Error( + sprintf(ERROR_MESSAGES.DATAFILE_AND_SDK_KEY_MISSING, MODULE_NAME) + ); this.readyPromise = Promise.resolve({ success: false, reason: getErrorMessage(datafileAndSdkKeyMissingError), @@ -88,7 +86,7 @@ export class ProjectConfigManager { handleNewDatafileException = this.handleNewDatafile(config.datafile); } - if (config.sdkKey && config.datafileManager) { + if (config.sdkKey && config.datafileManager) { this.datafileManager = config.datafileManager; this.datafileManager.start(); this.readyPromise = this.datafileManager @@ -105,7 +103,7 @@ export class ProjectConfigManager { reason: getErrorMessage(handleNewDatafileException, 'Invalid datafile'), }); } - } catch (ex: any) { + } catch (ex) { logger.error(ex); this.readyPromise = Promise.resolve({ success: false, @@ -137,7 +135,7 @@ export class ProjectConfigManager { return { success: false, reason: getErrorMessage(null, 'Datafile manager is not provided'), - } + }; } /** @@ -180,7 +178,7 @@ export class ProjectConfigManager { const { configObj, error } = tryCreatingProjectConfig({ datafile: newDatafile, jsonSchemaValidator: this.jsonSchemaValidator, - logger: logger + logger: logger, }); if (error) { @@ -190,7 +188,7 @@ export class ProjectConfigManager { if (configObj && oldRevision !== configObj.revision) { this.configObj = configObj; this.optimizelyConfigObj = null; - this.updateListeners.forEach((listener) => listener(configObj)); + this.updateListeners.forEach(listener => listener(configObj)); } } @@ -248,7 +246,7 @@ export class ProjectConfigManager { * @param {Function} listener * @return {Function} */ - onUpdate(listener: (config: ProjectConfig) => void): (() => void) { + onUpdate(listener: (config: ProjectConfig) => void): () => void { this.updateListeners.push(listener); return () => { const index = this.updateListeners.indexOf(listener); diff --git a/packages/optimizely-sdk/lib/export_types.ts b/packages/optimizely-sdk/lib/export_types.ts index 1903b2604..a41272060 100644 --- a/packages/optimizely-sdk/lib/export_types.ts +++ b/packages/optimizely-sdk/lib/export_types.ts @@ -44,4 +44,5 @@ export { TrackListenerPayload, NotificationCenter, OptimizelySegmentOption, + ICache, } from './shared_types'; diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index c6f2f4d51..70d44ef5d 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -30,6 +30,7 @@ import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import { OdpConfig } from './core/odp/odp_config'; import { BrowserOdpEventManager } from './plugins/odp/event_manager/index.browser'; import { BrowserOdpEventApiManager } from './plugins/odp/event_api_manager/index.browser'; +import { OdpEvent } from './core/odp/odp_event'; var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; @@ -604,6 +605,7 @@ describe('javascript-sdk (Browser)', function() { beforeEach(function() { sandbox.stub(logger, 'log'); sandbox.stub(logger, 'error'); + sandbox.stub(logger, 'warn'); clock = sinon.useFakeTimers(new Date()); }); @@ -714,7 +716,7 @@ describe('javascript-sdk (Browser)', function() { it('should accept a valid custom odp segment manager', async () => { const fakeSegmentManager = { - fetchQualifiedSegments: sinon.spy(), + fetchQualifiedSegments: sinon.stub().returns(['a']), updateSettings: sinon.spy(), }; @@ -735,7 +737,8 @@ describe('javascript-sdk (Browser)', function() { assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); - await client.fetchQualifiedSegments(testVuid); + const segments = await client.fetchQualifiedSegments(testVuid); + assert.deepEqual(segments, ['a']); sinon.assert.notCalled(logger.error); sinon.assert.called(fakeSegmentManager.fetchQualifiedSegments); @@ -770,7 +773,7 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.called(fakeEventManager.start); }); - it('should send an odp event with sendOdpEvent', async () => { + it('should send an odp event when calling sendOdpEvent with valid parameters', async () => { const fakeEventManager = { updateSettings: sinon.spy(), start: sinon.spy(), @@ -802,6 +805,149 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.called(fakeEventManager.sendEvent); }); + it('should convert fs-user-id, FS-USER-ID, and FS_USER_ID to fs_user_id identifier when calling sendOdpEvent', async () => { + const fakeEventManager = { + updateSettings: sinon.spy(), + start: sinon.spy(), + stop: sinon.spy(), + registerVuid: sinon.spy(), + identifyUser: sinon.spy(), + sendEvent: sinon.spy(), + flush: sinon.spy(), + }; + + const client = optimizelyFactory.createInstance({ + datafile: testData.getOdpIntegratedConfigWithSegments(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpOptions: { + eventManager: fakeEventManager, + }, + }); + + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + + // fs-user-id + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['fs-user-id', 'fsUserA']])); + sinon.assert.notCalled(logger.error); + sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); + + const sendEventArgs1 = fakeEventManager.sendEvent.args; + assert.deepEqual( + sendEventArgs1[0].toString(), + new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() + ); + + // FS-USER-ID + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['FS-USER-ID', 'fsUserA']])); + sinon.assert.notCalled(logger.error); + sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); + + const sendEventArgs2 = fakeEventManager.sendEvent.args; + assert.deepEqual( + sendEventArgs2[0].toString(), + new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() + ); + + // FS_USER_ID + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['FS_USER_ID', 'fsUserA']])); + sinon.assert.notCalled(logger.error); + sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); + + const sendEventArgs3 = fakeEventManager.sendEvent.args; + assert.deepEqual( + sendEventArgs3[0].toString(), + new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() + ); + + // fs_user_id + client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['fs_user_id', 'fsUserA']])); + sinon.assert.notCalled(logger.error); + sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); + + const sendEventArgs4 = fakeEventManager.sendEvent.args; + assert.deepEqual( + sendEventArgs4[0].toString(), + new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() + ); + }); + + it('should throw an error and not send an odp event when calling sendOdpEvent with an invalid action input', async () => { + const fakeEventManager = { + updateSettings: sinon.spy(), + start: sinon.spy(), + stop: sinon.spy(), + registerVuid: sinon.spy(), + identifyUser: sinon.spy(), + sendEvent: sinon.spy(), + flush: sinon.spy(), + }; + + const client = optimizelyFactory.createInstance({ + datafile: testData.getOdpIntegratedConfigWithSegments(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpOptions: { + eventManager: fakeEventManager, + }, + }); + + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + + client.sendOdpEvent(''); + sinon.assert.called(logger.error); + + client.sendOdpEvent(null); + sinon.assert.calledTwice(logger.error); + + client.sendOdpEvent(undefined); + sinon.assert.calledThrice(logger.error); + + sinon.assert.notCalled(fakeEventManager.sendEvent); + }); + + it('should use fullstack as a fallback value for the odp event when calling sendOdpEvent with an empty type input', async () => { + const fakeEventManager = { + updateSettings: sinon.spy(), + start: sinon.spy(), + stop: sinon.spy(), + registerVuid: sinon.spy(), + identifyUser: sinon.spy(), + sendEvent: sinon.spy(), + flush: sinon.spy(), + }; + + const client = optimizelyFactory.createInstance({ + datafile: testData.getOdpIntegratedConfigWithSegments(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpOptions: { + eventManager: fakeEventManager, + }, + }); + + const readyData = await client.onReady(); + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + + client.sendOdpEvent('dummy-action', ''); + + const sendEventArgs = fakeEventManager.sendEvent.args; + + const expectedEventArgs = new OdpEvent('fullstack', 'dummy-action', new Map(), new Map()); + assert.deepEqual(JSON.stringify(sendEventArgs[0][0]), JSON.stringify(expectedEventArgs)); + }); + it('should log an error when attempting to send an odp event when odp is disabled', async () => { const client = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 7b1f30dc3..8c226493b 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -4588,6 +4588,25 @@ describe('lib/optimizely', function() { assert.deepEqual(userId, user.getUserId()); }); + it('should create OptimizelyUserContext when input userId is an empty string', function() { + var userId = ''; + var user = optlyInstance.createUserContext(userId); + assert.instanceOf(user, OptimizelyUserContext); + assert.deepEqual(optlyInstance, user.getOptimizely()); + assert.deepEqual({}, user.getAttributes()); + assert.deepEqual(userId, user.getUserId()); + }); + + it('should return null OptimizelyUserContext when input userId is null', function() { + var user = optlyInstance.createUserContext(null); + assert.deepEqual(null, user); + }); + + it('should return null OptimizelyUserContext when input userId is undefined', function() { + var user = optlyInstance.createUserContext(undefined); + assert.deepEqual(null, user); + }); + it('should create multiple instances of OptimizelyUserContext', function() { var userId1 = 'testUser1'; var userId2 = 'testUser2'; @@ -10150,36 +10169,36 @@ describe('lib/optimizely', function() { }); it('should send an identify event when called with odp enabled', () => { - //... + // TODO }); it('should flush the odp event queue as part of the close() function call', () => { - //... + // TODO }); describe('odp manager overrides', () => { it('should accept custom cache size and timeout overrides defined in odp service config', () => { - //... + // TODO }); it('should accept a valid custom cache', () => { - //... + // TODO }); it('should call logger with log level of "error" when custom cache is invalid', () => { - //... + // TODO }); it('should accept a custom segment mananger override defined in odp service config', () => { - //... + // TODO }); it('should accept a custom event manager override defined in odp service config', () => { - //... + // TODO }); it('should call logger with log level of "error" when odp service config is invalid', () => { - //... + // TODO }); }); }); diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index d31139374..006f88c98 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -19,7 +19,7 @@ import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; import { EventProcessor } from '../modules/event_processor'; -import { OdpManager } from '../core/odp/odp_manager'; +import { IOdpManager } from '../core/odp/odp_manager'; import { OdpConfig } from '../core/odp/odp_config'; import { OdpEvent } from '../core/odp/odp_event'; import { OptimizelySegmentOption } from '../core/odp/optimizely_segment_option'; @@ -36,6 +36,7 @@ import { OptimizelyOptions, OptimizelyDecideOption, OptimizelyDecision, + Client, } from '../shared_types'; import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; @@ -64,6 +65,8 @@ import { NODE_CLIENT_ENGINE, NODE_CLIENT_VERSION, ODP_DEFAULT_EVENT_TYPE, + FS_USER_ID_ALIAS, + ODP_USER_KEY, } from '../utils/enums'; const MODULE_NAME = 'OPTIMIZELY'; @@ -75,7 +78,7 @@ type InputKey = 'feature_key' | 'user_id' | 'variable_key' | 'experiment_key' | type StringInputs = Partial>; -export default class Optimizely { +export default class Optimizely implements Client { private isOptimizelyConfigValid: boolean; private disposeOnUpdate: (() => void) | null; private readyPromise: Promise<{ success: boolean; reason?: string }>; @@ -91,7 +94,7 @@ export default class Optimizely { private decisionService: DecisionService; private eventProcessor: EventProcessor; private defaultDecideOptions: { [key: string]: boolean }; - protected odpManager?: OdpManager; + protected odpManager?: IOdpManager; public notificationCenter: NotificationCenter; constructor(config: OptimizelyOptions) { @@ -138,7 +141,12 @@ export default class Optimizely { configObj.revision, configObj.projectId ); + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + + NotificationRegistry.getNotificationCenter(config.sdkKey)?.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE + ); }); const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); @@ -1456,7 +1464,11 @@ export default class Optimizely { userIdentifier = userId; } - if (!userIdentifier || !this.validateInputs({ user_id: userIdentifier }, attributes)) { + if ( + userIdentifier === null || + userIdentifier === undefined || + !this.validateInputs({ user_id: userIdentifier }, attributes) + ) { return null; } @@ -1702,8 +1714,27 @@ export default class Optimizely { const odpEventType = type ?? ODP_DEFAULT_EVENT_TYPE; + const odpIdentifiers = new Map(identifiers); + + if (identifiers && identifiers.size > 0) { + try { + identifiers.forEach((identifier_value, identifier_key) => { + // Catch for fs-user-id, FS-USER-ID, and FS_USER_ID and assign value to fs_user_id identifier. + if ( + FS_USER_ID_ALIAS === identifier_key.toLowerCase() || + ODP_USER_KEY.FS_USER_ID === identifier_key.toLowerCase() + ) { + odpIdentifiers.delete(identifier_key); + odpIdentifiers.set(ODP_USER_KEY.FS_USER_ID, identifier_value); + } + }); + } catch (e) { + this.logger.warn(LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); + } + } + try { - const odpEvent = new OdpEvent(odpEventType, action, identifiers, data); + const odpEvent = new OdpEvent(odpEventType, action, odpIdentifiers, data); this.odpManager.sendEvent(odpEvent); } catch (e) { this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED, e); diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js index 239580a14..1b0d349ce 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js @@ -421,6 +421,32 @@ describe('lib/optimizely_user_context', function() { sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); + it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT string passed in decide options', function() { + var user = optlyInstance.createUserContext(userId); + var featureKey = 'feature_1'; + var variationKey = '3324490562'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey }); + var decision = user.decide(featureKey, ['INCLUDE_REASONS', 'DISABLE_DECISION_EVENT']); + assert.equal(decision.variationKey, variationKey); + assert.equal(decision.ruleKey, null); + assert.equal(decision.enabled, true); + assert.equal(decision.userContext.getUserId(), userId); + assert.deepEqual(decision.userContext.getAttributes(), {}); + assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); + assert.equal( + true, + decision.reasons.includes( + sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + ) + ); + + sinon.assert.notCalled(eventDispatcher.dispatchEvent); + }); + it('should return forced decision object when forced decision is set for a flag and dispatch an event', function() { var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; @@ -967,7 +993,7 @@ describe('lib/optimizely_user_context', function() { describe('fetchQualifiedSegments', () => { it('should successfully call fetch qualified segments', async () => { fakeOptimizely = { - fetchQualifiedSegments: sinon.stub().returns(true), + fetchQualifiedSegments: sinon.stub().returns(['a']), }; const user = new OptimizelyUserContext({ shouldIdentifyUser: false, @@ -980,7 +1006,7 @@ describe('lib/optimizely_user_context', function() { sinon.assert.calledWithExactly(fakeOptimizely.fetchQualifiedSegments, userId, undefined); - assert.deepEqual(user.qualifiedSegments, []); + assert.deepEqual(user.qualifiedSegments, ['a']); }); }); diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts index 73ed3f64d..301879214 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts @@ -33,7 +33,7 @@ interface OptimizelyUserContextConfig { } export interface IOptimizelyUserContext { - qualifiedSegments: string[]; + qualifiedSegments: string[] | null; getUserId(): string; getAttributes(): UserAttributes; setAttribute(key: string, value: unknown): void; @@ -54,7 +54,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { private userId: string; private attributes: UserAttributes; private forcedDecisionsMap: { [key: string]: { [key: string]: OptimizelyForcedDecision } }; - private _qualifiedSegments: string[] = []; + private _qualifiedSegments: string[] | null = null; constructor({ optimizely, userId, attributes, shouldIdentifyUser = true }: OptimizelyUserContextConfig) { this.optimizely = optimizely; @@ -96,12 +96,12 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { return this.optimizely; } - public get qualifiedSegments(): string[] { + public get qualifiedSegments(): string[] | null { return this._qualifiedSegments; } - public set qualifiedSegments(qualifiedSegments: string[]) { - this._qualifiedSegments = [...qualifiedSegments]; + public set qualifiedSegments(qualifiedSegments: string[] | null) { + this._qualifiedSegments = qualifiedSegments; } /** @@ -242,9 +242,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { userContext.forcedDecisionsMap = { ...this.forcedDecisionsMap }; } - if (this._qualifiedSegments) { - userContext._qualifiedSegments = [...this._qualifiedSegments]; - } + userContext._qualifiedSegments = this._qualifiedSegments; return userContext; } @@ -256,9 +254,8 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { */ async fetchQualifiedSegments(options?: OptimizelySegmentOption[]): Promise { const segments = await this.optimizely.fetchQualifiedSegments(this.userId, options); - if (segments) { - this.qualifiedSegments = [...segments]; - } + + this.qualifiedSegments = segments; return !!segments; } @@ -269,6 +266,10 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { * @returns {boolean} Boolean representing if a user qualified for the passed in segment. */ isQualifiedFor(segment: string): boolean { + if (!this._qualifiedSegments) { + return false; + } + return this._qualifiedSegments.indexOf(segment) > -1; } } diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts index 9ace87c16..8bc5f23a4 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts @@ -1,16 +1,20 @@ -import { OdpEventManager } from "../../../../lib/core/odp/odp_event_manager"; +import { IOdpEventManager, OdpEventManager } from '../../../../lib/core/odp/odp_event_manager'; import { LogLevel } from '../../../modules/logging'; const DEFAULT_BROWSER_QUEUE_SIZE = 100; -export class BrowserOdpEventManager extends OdpEventManager { - protected initParams(batchSize: number | undefined, queueSize: number | undefined, flushInterval: number | undefined): void { +export class BrowserOdpEventManager extends OdpEventManager implements IOdpEventManager { + protected initParams( + batchSize: number | undefined, + queueSize: number | undefined, + flushInterval: number | undefined + ): void { this.queueSize = queueSize || DEFAULT_BROWSER_QUEUE_SIZE; // disable event batching for browser this.batchSize = 1; this.flushInterval = 0; - + if (typeof batchSize !== 'undefined' && batchSize !== 1) { this.getLogger().log(LogLevel.WARNING, 'ODP event batch size must be 1 in the browser.'); } @@ -24,4 +28,4 @@ export class BrowserOdpEventManager extends OdpEventManager { // in Browser/client-side context, give debug message but leave events in queue this.getLogger().log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); } -} \ No newline at end of file +} diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts index 83ddf0296..3718a6f1c 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts @@ -1,14 +1,17 @@ - -import { OdpEvent } from "../../../../lib/core/odp/odp_event"; -import { OdpEventManager } from "../../../../lib/core/odp/odp_event_manager"; -import { LogLevel } from "../../../../lib/modules/logging"; +import { OdpEvent } from '../../../../lib/core/odp/odp_event'; +import { IOdpEventManager, OdpEventManager } from '../../../../lib/core/odp/odp_event_manager'; +import { LogLevel } from '../../../../lib/modules/logging'; const DEFAULT_BATCH_SIZE = 10; const DEFAULT_FLUSH_INTERVAL_MSECS = 1000; const DEFAULT_SERVER_QUEUE_SIZE = 10000; -export class NodeOdpEventManager extends OdpEventManager { - protected initParams(batchSize: number | undefined, queueSize: number | undefined, flushInterval: number | undefined): void { +export class NodeOdpEventManager extends OdpEventManager implements IOdpEventManager { + protected initParams( + batchSize: number | undefined, + queueSize: number | undefined, + flushInterval: number | undefined + ): void { this.queueSize = queueSize || DEFAULT_SERVER_QUEUE_SIZE; this.batchSize = batchSize || DEFAULT_BATCH_SIZE; @@ -26,4 +29,4 @@ export class NodeOdpEventManager extends OdpEventManager { this.getLogger().log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); this.queue = new Array(); } -} \ No newline at end of file +} diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index f0915931a..ff6df7bd7 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -25,13 +25,16 @@ import { EventProcessor } from '../lib/modules/event_processor'; import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; import { NOTIFICATION_TYPES } from './utils/enums'; -import { OdpManager } from './core/odp/odp_manager'; -import { OdpSegmentManager } from './core/odp/odp_segment_manager'; -import { LRUCache } from './utils/lru_cache'; -import { OdpEventManager } from './core/odp/odp_event_manager'; +import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; + +import { ICache } from './utils/lru_cache'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { OptimizelySegmentOption } from './core/odp/optimizely_segment_option'; -import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; +import { IOdpSegmentApiManager } from './core/odp/odp_segment_api_manager'; +import { IOdpSegmentManager } from './core/odp/odp_segment_manager'; +import { IOdpEventApiManager } from './core/odp/odp_event_api_manager'; +import { IOdpEventManager } from './core/odp/odp_event_manager'; +import { IOdpManager } from './core/odp/odp_manager'; export interface BucketerParams { experimentId: string; @@ -90,18 +93,18 @@ export interface DatafileOptions { export interface OdpOptions { disabled?: boolean; - segmentsCache?: LRUCache; + segmentsCache?: ICache; segmentsCacheSize?: number; segmentsCacheTimeout?: number; segmentsApiTimeout?: number; segmentsRequestHandler?: RequestHandler; - segmentManager?: OdpSegmentManager; + segmentManager?: IOdpSegmentManager; eventFlushInterval?: number; eventBatchSize?: number; eventQueueSize?: number; eventApiTimeout?: number; eventRequestHandler?: RequestHandler; - eventManager?: OdpEventManager; + eventManager?: IOdpEventManager; } export interface ListenerPayload { @@ -290,7 +293,7 @@ export interface OptimizelyOptions { sdkKey?: string; userProfileService?: UserProfileService | null; defaultDecideOptions?: OptimizelyDecideOption[]; - odpManager?: OdpManager; + odpManager?: IOdpManager; notificationCenter: NotificationCenterImpl; } @@ -533,4 +536,15 @@ export interface OptimizelyForcedDecision { variationKey: string; } -export { OptimizelySegmentOption }; +// ODP Exports + +export { + ICache, + RequestHandler, + OptimizelySegmentOption, + IOdpSegmentApiManager, + IOdpSegmentManager, + IOdpEventApiManager, + IOdpEventManager, + IOdpManager, +}; diff --git a/packages/optimizely-sdk/lib/tests/test_data.js b/packages/optimizely-sdk/lib/tests/test_data.js index 849cc5e86..4ac9a1b37 100644 --- a/packages/optimizely-sdk/lib/tests/test_data.js +++ b/packages/optimizely-sdk/lib/tests/test_data.js @@ -343,9 +343,7 @@ var decideConfig = { { experiments: [ { - audienceIds: [ - '13389130056' - ], + audienceIds: ['13389130056'], forcedVariations: {}, id: '3332020515', key: '3332020515', @@ -354,22 +352,20 @@ var decideConfig = { trafficAllocation: [ { endOfRange: 10000, - entityId: '3324490633' - } + entityId: '3324490633', + }, ], variations: [ { featureEnabled: true, id: '3324490633', key: '3324490633', - variables: [] - } - ] + variables: [], + }, + ], }, { - audienceIds: [ - '12208130097' - ], + audienceIds: ['12208130097'], forcedVariations: {}, id: '3332020494', key: '3332020494', @@ -378,17 +374,17 @@ var decideConfig = { trafficAllocation: [ { endOfRange: 0, - entityId: '3324490562' - } + entityId: '3324490562', + }, ], variations: [ { featureEnabled: true, id: '3324490562', key: '3324490562', - variables: [] - } - ] + variables: [], + }, + ], }, { status: 'Running', @@ -398,8 +394,8 @@ var decideConfig = { variables: [], id: '18257766532', key: '18257766532', - featureEnabled: true - } + featureEnabled: true, + }, ], id: '18322080788', key: '18322080788', @@ -407,14 +403,14 @@ var decideConfig = { trafficAllocation: [ { entityId: '18257766532', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], - forcedVariations: {} - } + forcedVariations: {}, + }, ], - id: '3319450668' - } + id: '3319450668', + }, ], anonymizeIP: true, botFiltering: true, @@ -424,9 +420,7 @@ var decideConfig = { variables: [], featureFlags: [ { - experimentIds: [ - '10390977673' - ], + experimentIds: ['10390977673'], id: '4482920077', key: 'feature_1', rolloutId: '3319450668', @@ -435,48 +429,46 @@ var decideConfig = { defaultValue: '42', id: '2687470095', key: 'i_42', - type: 'integer' + type: 'integer', }, { defaultValue: '4.2', id: '2689280165', key: 'd_4_2', - type: 'double' + type: 'double', }, { defaultValue: 'true', id: '2689660112', key: 'b_true', - type: 'boolean' + type: 'boolean', }, { defaultValue: 'foo', id: '2696150066', key: 's_foo', - type: 'string' + type: 'string', }, { defaultValue: { - value: 1 + value: 1, }, id: '2696150067', key: 'j_1', type: 'string', - subType: 'json' + subType: 'json', }, { defaultValue: 'invalid', id: '2696150068', key: 'i_1', type: 'invalid', - subType: '' - } - ] + subType: '', + }, + ], }, { - experimentIds: [ - '10420810910' - ], + experimentIds: ['10420810910'], id: '4482920078', key: 'feature_2', rolloutId: '', @@ -485,17 +477,17 @@ var decideConfig = { defaultValue: '42', id: '2687470095', key: 'i_42', - type: 'integer' - } - ] + type: 'integer', + }, + ], }, { experimentIds: [], id: '44829230000', key: 'feature_3', rolloutId: '', - variables: [] - } + variables: [], + }, ], experiments: [ { @@ -505,27 +497,25 @@ var decideConfig = { trafficAllocation: [ { entityId: '10389729780', - endOfRange: 10000 - } - ], - audienceIds: [ - '13389141123' + endOfRange: 10000, + }, ], + audienceIds: ['13389141123'], variations: [ { variables: [], featureEnabled: true, id: '10389729780', - key: 'a' + key: 'a', }, { variables: [], id: '10416523121', - key: 'b' - } + key: 'b', + }, ], forcedVariations: {}, - id: '10390977673' + id: '10390977673', }, { status: 'Running', @@ -534,8 +524,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10418551353', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], audienceIds: [], variations: [ @@ -543,70 +533,73 @@ var decideConfig = { variables: [], featureEnabled: true, id: '10418551353', - key: 'variation_with_traffic' + key: 'variation_with_traffic', }, { variables: [], featureEnabled: false, id: '10418510624', - key: 'variation_no_traffic' - } + key: 'variation_no_traffic', + }, ], forcedVariations: {}, - id: '10420810910' - } + id: '10420810910', + }, ], audiences: [ { id: '13389141123', - conditions: '["and",["or",["or",{ "match": "exact", "name": "gender", "type": "custom_attribute", "value": "f"}]]]', - name: 'gender' + conditions: + '["and",["or",["or",{ "match": "exact", "name": "gender", "type": "custom_attribute", "value": "f"}]]]', + name: 'gender', }, { id: '13389130056', - conditions: '["and",["or",["or",{ "match": "exact","name": "country","type": "custom_attribute","value": "US"}]]]', - name: 'US' + conditions: + '["and",["or",["or",{ "match": "exact","name": "country","type": "custom_attribute","value": "US"}]]]', + name: 'US', }, { id: '12208130097', - conditions: '["and",["or",["or",{"match": "exact","name": "browser","type": "custom_attribute","value": "safari"}]]]', - name: 'safari' + conditions: + '["and",["or",["or",{"match": "exact","name": "browser","type": "custom_attribute","value": "safari"}]]]', + name: 'safari', }, { - id: "age_18", + id: 'age_18', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "custom_attribute","value": 18}]]]', - name: 'age_18' + name: 'age_18', }, { id: 'invalid_format', conditions: '[]', - name: 'invalid_format' + name: 'invalid_format', }, { id: 'invalid_condition', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "custom_attribute","value": "US"}]]]', - name: 'invalid_condition' + name: 'invalid_condition', }, { id: 'invalid_type', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "invalid","value": 18}]]]', - name: 'invalid_type' + name: 'invalid_type', }, { id: 'invalid_match', conditions: '["and",["or",["or",{"match": "invalid","name": "age","type": "custom_attribute","value": 18}]]]', - name: 'invalid_match' + name: 'invalid_match', }, { id: 'nil_value', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "custom_attribute"}]]]', - name: 'nil_value' + name: 'nil_value', }, { id: 'invalid_name', conditions: '["and",["or",["or",{"match": "gt","type": "custom_attribute","value": 18}]]]', - name: 'invalid_name' - } + name: 'invalid_name', + }, ], groups: [ { @@ -614,8 +607,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10390965532', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], experiments: [ { @@ -625,8 +618,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10389752311', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], audienceIds: [], variations: [ @@ -634,11 +627,11 @@ var decideConfig = { variables: [], featureEnabled: false, id: '10389752311', - key: 'a' - } + key: 'a', + }, ], forcedVariations: {}, - id: '10390965532' + id: '10390965532', }, { status: 'Running', @@ -647,8 +640,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10418524243', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], audienceIds: [], variations: [ @@ -656,45 +649,40 @@ var decideConfig = { variables: [], featureEnabled: false, id: '10418524243', - key: 'a' - } + key: 'a', + }, ], forcedVariations: {}, - id: '10420843432' - } + id: '10420843432', + }, ], - id: '13142870430' - } + id: '13142870430', + }, ], attributes: [ { id: '10401066117', - key: 'gender' + key: 'gender', }, { id: '10401066170', - key: 'testvar' - } + key: 'testvar', + }, ], accountId: '10367498574', events: [ { - experimentIds: [ - '10420810910' - ], + experimentIds: ['10420810910'], id: '10404198134', - key: 'event1' + key: 'event1', }, { - experimentIds: [ - '10420810910', - '10390977673' - ], + experimentIds: ['10420810910', '10390977673'], id: '10404198135', - key: 'event_multiple_running_exp_attached' - } + key: 'event_multiple_running_exp_attached', + }, ], - revision: '241' + revision: '241', }; export var getParsedAudiences = [ @@ -705,11 +693,11 @@ export var getParsedAudiences = [ }, ]; -export var getTestProjectConfig = function () { +export var getTestProjectConfig = function() { return cloneDeep(config); }; -export var getTestDecideProjectConfig = function () { +export var getTestDecideProjectConfig = function() { return cloneDeep(decideConfig); }; @@ -1044,30 +1032,27 @@ var configWithFeatures = { key: 'test_experiment3', status: 'Running', layerId: '6', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], id: '111134', forcedVariations: {}, trafficAllocation: [ { entityId: '222239', - endOfRange: 2500 + endOfRange: 2500, }, { entityId: '', - endOfRange: 5000 + endOfRange: 5000, }, { entityId: '', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variations: [ { @@ -1075,33 +1060,30 @@ var configWithFeatures = { key: 'control', variables: [], featureEnabled: false, - } + }, ], }, { key: 'test_experiment4', status: 'Running', layerId: '7', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], id: '111135', forcedVariations: {}, trafficAllocation: [ { entityId: '222240', - endOfRange: 5000 + endOfRange: 5000, }, { entityId: '', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variations: [ { @@ -1109,29 +1091,26 @@ var configWithFeatures = { key: 'control', variables: [], featureEnabled: false, - } + }, ], }, { key: 'test_experiment5', status: 'Running', layerId: '8', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], id: '111136', forcedVariations: {}, trafficAllocation: [ { entityId: '222241', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variations: [ { @@ -1139,7 +1118,7 @@ var configWithFeatures = { key: 'control', variables: [], featureEnabled: false, - } + }, ], }, ], @@ -1159,7 +1138,7 @@ var configWithFeatures = { name: 'Test attribute users 3', conditions: '["and", ["or", ["or", {"match": "exact", "name": "experiment_attr", "type": "custom_attribute", "value": "group_experiment"}]]]', - } + }, ], revision: '35', groups: [ @@ -1315,10 +1294,7 @@ var configWithFeatures = { id: '42222', key: 'group_2_exp_1', status: 'Running', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], layerId: '211183', variations: [ @@ -1332,25 +1308,22 @@ var configWithFeatures = { trafficAllocation: [ { entityId: '38901', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variationKeyMap: { var_1: { key: 'var_1', id: '38901', featureEnabled: false, - } - } + }, + }, }, { id: '42223', key: 'group_2_exp_2', status: 'Running', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], layerId: '211184', variations: [ @@ -1364,18 +1337,15 @@ var configWithFeatures = { trafficAllocation: [ { entityId: '38905', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], }, { id: '42224', key: 'group_2_exp_3', status: 'Running', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], layerId: '211185', variations: [ @@ -1389,30 +1359,30 @@ var configWithFeatures = { trafficAllocation: [ { entityId: '38906', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], - } + }, ], trafficAllocation: [ { entityId: '42222', - endOfRange: 2500 + endOfRange: 2500, }, { entityId: '42223', - endOfRange: 5000 + endOfRange: 5000, }, { entityId: '42224', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 + endOfRange: 10000, }, ], - } + }, ], attributes: [ { @@ -1457,9 +1427,9 @@ var configWithFeatures = { value: 'Hello audience', }, { - id: "8765345281230956", + id: '8765345281230956', value: '{ "count": 2, "message": "Hello audience" }', - } + }, ], }, ], @@ -1502,7 +1472,7 @@ var configWithFeatures = { { id: '8765345281230956', value: '{ "count": 1, "message": "Hello" }', - } + }, ], }, ], @@ -1638,7 +1608,7 @@ var configWithFeatures = { variables: [], }; -export var getTestProjectConfigWithFeatures = function () { +export var getTestProjectConfigWithFeatures = function() { return cloneDeep(configWithFeatures); }; @@ -2005,7 +1975,7 @@ export var datafileWithFeaturesExpectedData = { 8765345281230956: { id: '8765345281230956', value: '{ "count": 2, "message": "Hello audience" }', - } + }, }, 594038: { 4919852825313280: { @@ -2027,7 +1997,7 @@ export var datafileWithFeaturesExpectedData = { 8765345281230956: { id: '8765345281230956', value: '{ "count": 1, "message": "Hello" }', - } + }, }, 594061: { 5060590313668608: { @@ -2330,7 +2300,7 @@ export var datafileWithFeaturesExpectedData = { type: 'json', key: 'button_info', id: '1547854156498475', - defaultValue: "{ \"num_buttons\": 0, \"text\": \"default value\"}" + defaultValue: '{ "num_buttons": 0, "text": "default value"}', }, ], experimentIds: ['594098'], @@ -2363,7 +2333,7 @@ export var datafileWithFeaturesExpectedData = { id: '6199684360044544', }, button_info: { - defaultValue: "{ \"num_buttons\": 0, \"text\": \"default value\"}", + defaultValue: '{ "num_buttons": 0, "text": "default value"}', id: '1547854156498475', key: 'button_info', type: 'json', @@ -2741,7 +2711,7 @@ var unsupportedVersionConfig = { projectId: '111001', }; -export var getUnsupportedVersionConfig = function () { +export var getUnsupportedVersionConfig = function() { return cloneDeep(unsupportedVersionConfig); }; @@ -3141,267 +3111,298 @@ var typedAudiencesConfig = { revision: '3', }; -export var getTypedAudiencesConfig = function () { +export var getTypedAudiencesConfig = function() { return cloneDeep(typedAudiencesConfig); }; var odpIntegratedConfigWithSegments = { - "version": "4", - "sendFlagDecisions": true, - "rollouts": [ - { - "experiments": [ - { - "audienceIds": ["13389130056"], - "forcedVariations": {}, - "id": "3332020515", - "key": "rollout-rule-1", - "layerId": "3319450668", - "status": "Running", - "trafficAllocation": [ - { - "endOfRange": 10000, - "entityId": "3324490633" - } - ], - "variations": [ - { - "featureEnabled": true, - "id": "3324490633", - "key": "rollout-variation-on", - "variables": [] - } - ] - }, - { - "audienceIds": [], - "forcedVariations": {}, - "id": "3332020556", - "key": "rollout-rule-2", - "layerId": "3319450668", - "status": "Running", - "trafficAllocation": [ - { - "endOfRange": 10000, - "entityId": "3324490644" - } - ], - "variations": [ - { - "featureEnabled": false, - "id": "3324490644", - "key": "rollout-variation-off", - "variables": [] - } - ] - } - ], - "id": "3319450668" - } + version: '4', + sendFlagDecisions: true, + rollouts: [ + { + experiments: [ + { + audienceIds: ['13389130056'], + forcedVariations: {}, + id: '3332020515', + key: 'rollout-rule-1', + layerId: '3319450668', + status: 'Running', + trafficAllocation: [ + { + endOfRange: 10000, + entityId: '3324490633', + }, + ], + variations: [ + { + featureEnabled: true, + id: '3324490633', + key: 'rollout-variation-on', + variables: [], + }, + ], + }, + { + audienceIds: [], + forcedVariations: {}, + id: '3332020556', + key: 'rollout-rule-2', + layerId: '3319450668', + status: 'Running', + trafficAllocation: [ + { + endOfRange: 10000, + entityId: '3324490644', + }, + ], + variations: [ + { + featureEnabled: false, + id: '3324490644', + key: 'rollout-variation-off', + variables: [], + }, + ], + }, + ], + id: '3319450668', + }, ], - "anonymizeIP": true, - "botFiltering": true, - "projectId": "10431130345", - "variables": [], - "featureFlags": [ - { - "experimentIds": ["10390977673"], - "id": "4482920077", - "key": "flag-segment", - "rolloutId": "3319450668", - "variables": [ - { - "defaultValue": "42", - "id": "2687470095", - "key": "i_42", - "type": "integer" - } - ] - } + anonymizeIP: true, + botFiltering: true, + projectId: '10431130345', + variables: [], + featureFlags: [ + { + experimentIds: ['10390977673'], + id: '4482920077', + key: 'flag-segment', + rolloutId: '3319450668', + variables: [ + { + defaultValue: '42', + id: '2687470095', + key: 'i_42', + type: 'integer', + }, + ], + }, ], - "experiments": [ + experiments: [ { - "status": "Running", - "key": "experiment-segment", - "layerId": "10420273888", - "trafficAllocation": [ + status: 'Running', + key: 'experiment-segment', + layerId: '10420273888', + trafficAllocation: [ { - "entityId": "10389729780", - "endOfRange": 10000 - } + entityId: '10389729780', + endOfRange: 10000, + }, ], - "audienceIds": ["$opt_dummy_audience"], - "audienceConditions": ["or", "13389142234", "13389141123"], - "variations": [ + audienceIds: ['$opt_dummy_audience'], + audienceConditions: ['or', '13389142234', '13389141123'], + variations: [ { - "variables": [], - "featureEnabled": true, - "id": "10389729780", - "key": "variation-a" + variables: [], + featureEnabled: true, + id: '10389729780', + key: 'variation-a', }, { - "variables": [], - "id": "10416523121", - "key": "variation-b" - } + variables: [], + id: '10416523121', + key: 'variation-b', + }, ], - "forcedVariations": {}, - "id": "10390977673" - } + forcedVariations: {}, + id: '10390977673', + }, ], - "groups": [], - "integrations": [ + groups: [], + integrations: [ { - "key": "odp", - "host": "https://api.zaius.com/", - "publicKey": "W4WzcEs-ABgXorzY7h1LCQ" + key: 'odp', + host: 'https://api.zaius.com/', + publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', }, { - "key": "odp", - "a": "1", - "b": "2", + key: 'odp', + host: 'https://api.zzzzaius.com/', + publicKey: 'W4WzcEs-ABgXorzssssY7h1LCQ', }, { - "key": "x", - "test": "foobar" - } + key: 'odp', + a: '1', + b: '2', + }, + { + key: 'x', + test: 'foobar', + }, ], - "typedAudiences": [ + typedAudiences: [ { - "id": "13389142234", - "conditions": [ - "and", + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }, { - "id": "13389142234", - "conditions": [ - "and", + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }, { - "id": "13389130056", - "conditions": [ - "and", + id: '13389130056', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-2", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', }, { - "value": "us", - "type": "custom_attribute", - "name": "country", - "match": "exact" - } + value: 'us', + type: 'custom_attribute', + name: 'country', + match: 'exact', + }, ], [ - "or", - { - "value": "odp-segment-3", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] - ], - "name": "odp-segment-2" - } + 'or', + { + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], + ], + name: 'odp-segment-2', + }, ], - "audiences": [ + audiences: [ { - "id": "13389141123", - "conditions": "[\"and\", [\"or\", [\"or\", {\"match\": \"gt\", \"name\": \"age\", \"type\": \"custom_attribute\", \"value\": 20}]]]", - "name": "adult" - } + id: '13389141123', + conditions: '["and", ["or", ["or", {"match": "gt", "name": "age", "type": "custom_attribute", "value": 20}]]]', + name: 'adult', + }, ], - "attributes": [ + attributes: [ { - "id": "10401066117", - "key": "gender" + id: '10401066117', + key: 'gender', }, { - "id": "10401066170", - "key": "testvar" - } + id: '10401066170', + key: 'testvar', + }, ], - "accountId": "10367498574", - "events": [], - "revision": "101" -} + accountId: '10367498574', + events: [], + revision: '101', +}; -export var getOdpIntegratedConfigWithSegments = function () { +export var getOdpIntegratedConfigWithSegments = function() { return cloneDeep(odpIntegratedConfigWithSegments); }; var odpIntegratedConfigWithoutSegments = { - "version": "4", - "rollouts": [], - "anonymizeIP": true, - "projectId": "10431130345", - "variables": [], - "featureFlags": [], - "experiments": [], - "audiences": [], - "groups": [], - "attributes": [], - "accountId": "10367498574", - "events": [], - "integrations": [ - { - "key": "odp", - "host": "https://api.zaius.com/", - "publicKey": "W4WzcEs-ABgXorzY7h1LCQ" - }, - { - "key": "odp", - "a": "1", - "b": "2", - }, - { - "key": "x", - "test": "foobar" - } + version: '4', + rollouts: [], + anonymizeIP: true, + projectId: '10431130345', + variables: [], + featureFlags: [], + experiments: [], + audiences: [], + groups: [], + attributes: [], + accountId: '10367498574', + events: [], + integrations: [ + { + key: 'odp', + host: 'https://api.zaius.com/', + publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + }, + { + key: 'odp', + a: '1', + b: '2', + }, + { + key: 'x', + test: 'foobar', + }, ], - "revision": "100" -} + revision: '100', +}; -export var getOdpIntegratedConfigWithoutSegments = function () { +export var getOdpIntegratedConfigWithoutSegments = function() { return cloneDeep(odpIntegratedConfigWithoutSegments); }; +var odpIntegratedConfigWithoutKey = { + version: '4', + rollouts: [], + anonymizeIP: true, + projectId: '10431130345', + variables: [], + featureFlags: [], + experiments: [], + audiences: [], + groups: [], + attributes: [], + accountId: '10367498574', + events: [], + integrations: [ + { + host: 'https://api.zaius.com/', + publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + }, + ], + revision: '100', +}; + +export var getOdpIntegratedConfigWithoutKey = function() { + return cloneDeep(odpIntegratedConfigWithoutKey); +}; + export var typedAudiencesById = { 3468206642: { id: '3468206642', @@ -3536,7 +3537,7 @@ var mutexFeatureTestsConfig = { revision: '12', }; -export var getMutexFeatureTestsConfig = function () { +export var getMutexFeatureTestsConfig = function() { return cloneDeep(mutexFeatureTestsConfig); }; @@ -3544,13 +3545,13 @@ export var rolloutDecisionObj = { experiment: null, variation: null, decisionSource: 'rollout', -} +}; export var featureTestDecisionObj = { experiment: { trafficAllocation: [ { endOfRange: 5000, entityId: '594096' }, - { endOfRange: 10000, entityId: '594097' } + { endOfRange: 10000, entityId: '594097' }, ], layerId: '594093', forcedVariations: {}, @@ -3594,345 +3595,359 @@ export var featureTestDecisionObj = { variables: [], }, decisionSource: 'feature-test', -} +}; var similarRuleKeyConfig = { - version: "4", + version: '4', rollouts: [ { experiments: [ { - status: "Running", + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5452", - key: "on", - featureEnabled: true - } + id: '5452', + key: 'on', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000004981", + key: 'targeted_delivery', + layerId: '9300000004981', trafficAllocation: [ { - entityId: "5452", - endOfRange: 10000 - } + entityId: '5452', + endOfRange: 10000, + }, ], - id: "9300000004981" - }, { - status: "Running", + id: '9300000004981', + }, + { + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5451", - key: "off", - featureEnabled: false - } + id: '5451', + key: 'off', + featureEnabled: false, + }, ], forcedVariations: {}, - key: "default-rollout-2029-20301771717", - layerId: "default-layer-rollout-2029-20301771717", + key: 'default-rollout-2029-20301771717', + layerId: 'default-layer-rollout-2029-20301771717', trafficAllocation: [ { - entityId: "5451", - endOfRange: 10000 - } + entityId: '5451', + endOfRange: 10000, + }, ], - id: "default-rollout-2029-20301771717" - } + id: 'default-rollout-2029-20301771717', + }, ], - id: "rollout-2029-20301771717" - }, { + id: 'rollout-2029-20301771717', + }, + { experiments: [ { - status: "Running", + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5450", - key: "on", - featureEnabled: true - } + id: '5450', + key: 'on', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000004979", + key: 'targeted_delivery', + layerId: '9300000004979', trafficAllocation: [ { - entityId: "5450", - endOfRange: 10000 - } + entityId: '5450', + endOfRange: 10000, + }, ], - id: "9300000004979" - }, { - status: "Running", + id: '9300000004979', + }, + { + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5449", - key: "off", - featureEnabled: false - } + id: '5449', + key: 'off', + featureEnabled: false, + }, ], forcedVariations: {}, - key: "default-rollout-2028-20301771717", - layerId: "default-layer-rollout-2028-20301771717", + key: 'default-rollout-2028-20301771717', + layerId: 'default-layer-rollout-2028-20301771717', trafficAllocation: [ { - entityId: "5449", - endOfRange: 10000 - } + entityId: '5449', + endOfRange: 10000, + }, ], - id: "default-rollout-2028-20301771717" - } + id: 'default-rollout-2028-20301771717', + }, ], - id: "rollout-2028-20301771717" - }, { + id: 'rollout-2028-20301771717', + }, + { experiments: [ { - status: "Running", + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5448", - key: "on", - featureEnabled: true - } + id: '5448', + key: 'on', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000004977", + key: 'targeted_delivery', + layerId: '9300000004977', trafficAllocation: [ { - entityId: "5448", - endOfRange: 10000 - } + entityId: '5448', + endOfRange: 10000, + }, ], - id: "9300000004977" - }, { - status: "Running", + id: '9300000004977', + }, + { + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5447", - key: "off", - featureEnabled: false - } + id: '5447', + key: 'off', + featureEnabled: false, + }, ], forcedVariations: {}, - key: "default-rollout-2027-20301771717", - layerId: "default-layer-rollout-2027-20301771717", + key: 'default-rollout-2027-20301771717', + layerId: 'default-layer-rollout-2027-20301771717', trafficAllocation: [ { - entityId: "5447", - endOfRange: 10000 - } + entityId: '5447', + endOfRange: 10000, + }, ], - id: "default-rollout-2027-20301771717" - } + id: 'default-rollout-2027-20301771717', + }, ], - id: "rollout-2027-20301771717" - } + id: 'rollout-2027-20301771717', + }, ], typedAudiences: [], anonymizeIP: true, - projectId: "20286295225", + projectId: '20286295225', variables: [], featureFlags: [ { experimentIds: [], - rolloutId: "rollout-2029-20301771717", + rolloutId: 'rollout-2029-20301771717', variables: [], - id: "2029", - key: "flag_3" - }, { + id: '2029', + key: 'flag_3', + }, + { experimentIds: [], - rolloutId: "rollout-2028-20301771717", + rolloutId: 'rollout-2028-20301771717', variables: [], - id: "2028", - key: "flag_2" - }, { + id: '2028', + key: 'flag_2', + }, + { experimentIds: [], - rolloutId: "rollout-2027-20301771717", + rolloutId: 'rollout-2027-20301771717', variables: [], - id: "2027", - key: "flag_1" - } + id: '2027', + key: 'flag_1', + }, ], experiments: [], audiences: [ { - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - id: "$opt_dummy_audience", - name: "Optimizely-Generated Audience for Backwards Compatibility" - } + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + id: '$opt_dummy_audience', + name: 'Optimizely-Generated Audience for Backwards Compatibility', + }, ], groups: [], attributes: [], botFiltering: false, - accountId: "19947277778", + accountId: '19947277778', events: [], - revision: "11", - sendFlagDecisions: true -} + revision: '11', + sendFlagDecisions: true, +}; -export var getSimilarRuleKeyConfig = function () { +export var getSimilarRuleKeyConfig = function() { return cloneDeep(similarRuleKeyConfig); }; var similarExperimentKeysConfig = { - version: "4", + version: '4', rollouts: [], typedAudiences: [ { - id: "20415611520", + id: '20415611520', conditions: [ - "and", + 'and', [ - "or", + 'or', [ - "or", { + 'or', + { value: true, - type: "custom_attribute", - name: "hiddenLiveEnabled", - match: "exact" - } - ] - ] - ], - name: "test1" - }, { - id: "20406066925", + type: 'custom_attribute', + name: 'hiddenLiveEnabled', + match: 'exact', + }, + ], + ], + ], + name: 'test1', + }, + { + id: '20406066925', conditions: [ - "and", + 'and', [ - "or", + 'or', [ - "or", { + 'or', + { value: false, - type: "custom_attribute", - name: "hiddenLiveEnabled", - match: "exact" - } - ] - ] - ], - name: "test2" - } + type: 'custom_attribute', + name: 'hiddenLiveEnabled', + match: 'exact', + }, + ], + ], + ], + name: 'test2', + }, ], anonymizeIP: true, - projectId: "20430981610", + projectId: '20430981610', variables: [], featureFlags: [ { - experimentIds: ["9300000007569"], - rolloutId: "", + experimentIds: ['9300000007569'], + rolloutId: '', variables: [], - id: "3045", - key: "flag1" - }, { - experimentIds: ["9300000007573"], - rolloutId: "", + id: '3045', + key: 'flag1', + }, + { + experimentIds: ['9300000007573'], + rolloutId: '', variables: [], - id: "3046", - key: "flag2" - } + id: '3046', + key: 'flag2', + }, ], experiments: [ { - status: "Running", - audienceConditions: [ - "or", "20415611520" - ], - audienceIds: ["20415611520"], + status: 'Running', + audienceConditions: ['or', '20415611520'], + audienceIds: ['20415611520'], variations: [ { variables: [], - id: "8045", - key: "variation1", - featureEnabled: true - } + id: '8045', + key: 'variation1', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000007569", + key: 'targeted_delivery', + layerId: '9300000007569', trafficAllocation: [ { - entityId: "8045", - endOfRange: 10000 - } - ], - id: "9300000007569" - }, { - status: "Running", - audienceConditions: [ - "or", "20406066925" + entityId: '8045', + endOfRange: 10000, + }, ], - audienceIds: ["20406066925"], + id: '9300000007569', + }, + { + status: 'Running', + audienceConditions: ['or', '20406066925'], + audienceIds: ['20406066925'], variations: [ { variables: [], - id: "8048", - key: "variation2", - featureEnabled: true - } + id: '8048', + key: 'variation2', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000007573", + key: 'targeted_delivery', + layerId: '9300000007573', trafficAllocation: [ { - entityId: "8048", - endOfRange: 10000 - } + entityId: '8048', + endOfRange: 10000, + }, ], - id: "9300000007573" - } + id: '9300000007573', + }, ], audiences: [ { - id: "20415611520", - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - name: "test1" - }, { - id: "20406066925", - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - name: "test2" - }, { - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - id: "$opt_dummy_audience", - name: "Optimizely-Generated Audience for Backwards Compatibility" - } + id: '20415611520', + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + name: 'test1', + }, + { + id: '20406066925', + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + name: 'test2', + }, + { + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + id: '$opt_dummy_audience', + name: 'Optimizely-Generated Audience for Backwards Compatibility', + }, ], groups: [], attributes: [ { - id: "20408641883", - key: "hiddenLiveEnabled" - } + id: '20408641883', + key: 'hiddenLiveEnabled', + }, ], botFiltering: false, - accountId: "17882702980", + accountId: '17882702980', events: [], - revision: "25", - sendFlagDecisions: true -} + revision: '25', + sendFlagDecisions: true, +}; -export var getSimilarExperimentKeyConfig = function () { +export var getSimilarExperimentKeyConfig = function() { return cloneDeep(similarExperimentKeysConfig); }; @@ -3946,8 +3961,9 @@ export default { getTypedAudiencesConfig: getTypedAudiencesConfig, getOdpIntegratedConfigWithSegments: getOdpIntegratedConfigWithSegments, getOdpIntegratedConfigWithoutSegments: getOdpIntegratedConfigWithoutSegments, + getOdpIntegratedConfigWithoutKey: getOdpIntegratedConfigWithoutKey, typedAudiencesById: typedAudiencesById, getMutexFeatureTestsConfig: getMutexFeatureTestsConfig, getSimilarRuleKeyConfig: getSimilarRuleKeyConfig, - getSimilarExperimentKeyConfig: getSimilarExperimentKeyConfig + getSimilarExperimentKeyConfig: getSimilarExperimentKeyConfig, }; diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index 393a07028..56f3ca965 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -52,6 +52,7 @@ export const ERROR_MESSAGES = { INVALID_USER_ID: '%s: Provided user ID is in an invalid format.', INVALID_USER_PROFILE_SERVICE: '%s: Provided user profile service instance is in an invalid format: %s.', LOCAL_STORAGE_DOES_NOT_EXIST: 'Error accessing window localStorage.', + MISSING_INTEGRATION_KEY: '%s: Integration key missing from datafile. All integrations should include a key.', NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', @@ -122,6 +123,8 @@ export const LOG_MESSAGES = { ODP_DISABLED: 'ODP Disabled.', ODP_IDENTIFY_FAILED_ODP_DISABLED: '%s: ODP identify event for user %s is not dispatched (ODP disabled).', ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED: '%s: ODP identify event %s is not dispatched (ODP not integrated).', + ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED: + '%s: sendOdpEvent failed to parse through and convert fs_user_id aliases', PARSED_REVENUE_VALUE: '%s: Parsed revenue value "%s" from event tags.', PARSED_NUMERIC_VALUE: '%s: Parsed event value "%s" from event tags.', RETURNING_STORED_VARIATION: @@ -337,8 +340,8 @@ export enum NOTIFICATION_TYPES { * Default milliseconds before request timeout */ export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute -export const REQUEST_TIMEOUT_ODP_SEGMENTS_MS = 10 * 1000; // 10 secs -export const REQUEST_TIMEOUT_ODP_EVENTS_MS = 10 * 1000; // 10 secs +export const REQUEST_TIMEOUT_ODP_SEGMENTS_MS = 10 * 1000; // 10 secs +export const REQUEST_TIMEOUT_ODP_EVENTS_MS = 10 * 1000; // 10 secs /** * ODP User Key Options @@ -348,6 +351,11 @@ export enum ODP_USER_KEY { FS_USER_ID = 'fs_user_id', } +/** + * Alias for fs_user_id to catch for and automatically convert to fs_user_id + */ +export const FS_USER_ID_ALIAS = 'fs-user-id'; + export const ODP_DEFAULT_EVENT_TYPE = 'fullstack'; export const ODP_EVENT_BROWSER_ENDPOINT = 'https://jumbe.zaius.com/v2/zaius.gif'; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts index 343aa85cb..ca5d4cb92 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts @@ -29,8 +29,8 @@ export const BrowserLRUCacheConfig: ISegmentsCacheConfig = { export class BrowserLRUCache extends LRUCache { constructor(config?: BrowserLRUCacheConfig) { super({ - maxSize: config?.maxSize || BrowserLRUCacheConfig.DEFAULT_CAPACITY, - timeout: config?.timeout || BrowserLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, + maxSize: config?.maxSize?? BrowserLRUCacheConfig.DEFAULT_CAPACITY, + timeout: config?.timeout?? BrowserLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, }); } } diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/index.ts b/packages/optimizely-sdk/lib/utils/lru_cache/index.ts index 185093ead..fb7ada423 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/index.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/index.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { LRUCache } from './lru_cache'; +import { ICache, LRUCache } from './lru_cache'; import { BrowserLRUCache } from './browser_lru_cache'; import { ServerLRUCache } from './server_lru_cache'; -export { LRUCache, BrowserLRUCache, ServerLRUCache }; +export { ICache, LRUCache, BrowserLRUCache, ServerLRUCache }; diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts index 388ddfbd9..dae7f32df 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts @@ -22,13 +22,19 @@ export interface LRUCacheConfig { timeout: number; } +export interface ICache { + lookup(key: K): V | null; + save({ key, value }: { key: K; value: V }): void; + reset(): void; +} + /** * Least-Recently Used Cache (LRU Cache) Implementation with Generic Key-Value Pairs * Analogous to a Map that has a specified max size and a timeout per element. * - Removes the least-recently used element from the cache if max size exceeded. * - Removes stale elements (entries older than their timeout) from the cache. */ -export class LRUCache { +export class LRUCache implements ICache { private _map: Map> = new Map(); private _maxSize; // Defines maximum size of _map private _timeout; // Milliseconds each entry has before it becomes stale @@ -36,9 +42,11 @@ export class LRUCache { get map(): Map> { return this._map; } + get maxSize(): number { return this._maxSize; } + get timeout(): number { return this._timeout; } diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts index eaf72fafa..110d9b28e 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts @@ -29,8 +29,8 @@ export const ServerLRUCacheConfig: ISegmentsCacheConfig = { export class ServerLRUCache extends LRUCache { constructor(config?: ServerLRUCacheConfig) { super({ - maxSize: config?.maxSize || ServerLRUCacheConfig.DEFAULT_CAPACITY, - timeout: config?.timeout || ServerLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, + maxSize: config?.maxSize?? ServerLRUCacheConfig.DEFAULT_CAPACITY, + timeout: config?.timeout?? ServerLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, }); } } diff --git a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts index 9fb465dca..8a0ec3382 100644 --- a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts @@ -95,7 +95,7 @@ describe('OdpSegmentManager', () => { expect(segments).toBeNull; }); - it('should ignore the cache if the option is included in the options array.', async () => { + it('should ignore the cache if the option enum is included in the options array.', async () => { odpConfig.update(validTestOdpConfig); setCache(userKey, userValue, ['a']); options = [OptimizelySegmentOption.IGNORE_CACHE]; @@ -105,7 +105,18 @@ describe('OdpSegmentManager', () => { expect(cacheCount()).toBe(1); }); - it('should reset the cache if the option is included in the options array.', async () => { + it('should ignore the cache if the option string is included in the options array.', async () => { + odpConfig.update(validTestOdpConfig); + setCache(userKey, userValue, ['a']); + // @ts-ignore + options = ['IGNORE_CACHE']; + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); + expect(segments).toEqual(['new-customer']); + expect(cacheCount()).toBe(1); + }); + + it('should reset the cache if the option enum is included in the options array.', async () => { odpConfig.update(validTestOdpConfig); setCache(userKey, userValue, ['a']); setCache(userKey, '123', ['a']); @@ -118,6 +129,20 @@ describe('OdpSegmentManager', () => { expect(cacheCount()).toBe(1); }); + it('should reset the cache if the option string is included in the options array.', async () => { + odpConfig.update(validTestOdpConfig); + setCache(userKey, userValue, ['a']); + setCache(userKey, '123', ['a']); + setCache(userKey, '456', ['a']); + // @ts-ignore + options = ['RESET_CACHE']; + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); + expect(segments).toEqual(['new-customer']); + expect(peekCache(userKey, userValue)).toEqual(segments); + expect(cacheCount()).toBe(1); + }); + it('should make a valid cache key.', () => { expect('vuid-$-test-user').toBe(manager.makeCacheKey(userKey, userValue)); }); @@ -134,8 +159,8 @@ describe('OdpSegmentManager', () => { function peekCache(userKey: string, userValue: string): string[] | null { const cacheKey = manager.makeCacheKey(userKey, userValue); - return manager.segmentsCache.peek(cacheKey); + return (manager.segmentsCache as LRUCache).peek(cacheKey); } - const cacheCount = () => manager.segmentsCache.map.size; + const cacheCount = () => (manager.segmentsCache as LRUCache).map.size; }); From d11c8479da839ca7a1ec96684390729480d5c530 Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Wed, 3 May 2023 18:55:14 -0400 Subject: [PATCH 014/200] [FSSDK-9096] chore: Prepare release 5.0.0-beta (#822) --- packages/optimizely-sdk/CHANGELOG.md | 47 +++- .../optimizely-sdk/lib/index.browser.tests.js | 10 +- .../optimizely-sdk/lib/index.lite.tests.js | 28 +- .../optimizely-sdk/lib/index.node.tests.js | 52 ++-- .../optimizely-sdk/lib/utils/enums/index.ts | 4 +- packages/optimizely-sdk/package-lock.json | 2 +- packages/optimizely-sdk/package.json | 2 +- .../tests/index.react_native.spec.ts | 255 +++++++++--------- 8 files changed, 221 insertions(+), 179 deletions(-) diff --git a/packages/optimizely-sdk/CHANGELOG.md b/packages/optimizely-sdk/CHANGELOG.md index a99d86740..7e1b0a18c 100644 --- a/packages/optimizely-sdk/CHANGELOG.md +++ b/packages/optimizely-sdk/CHANGELOG.md @@ -7,7 +7,49 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -### Breaking Changes +## 5.0.0-beta +May 4, 2023 + +### New Features + +The 5.0.0-beta release introduces a new primary feature, [Advanced Audience Targeting]( https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) enabled through integration with [Optimizely Data Platform (ODP)](https://docs.developers.optimizely.com/optimizely-data-platform/docs) ([#765](https://github.com/optimizely/javascript-sdk/pull/765), [#775](https://github.com/optimizely/javascript-sdk/pull/775), [#776](https://github.com/optimizely/javascript-sdk/pull/776), [#777](https://github.com/optimizely/javascript-sdk/pull/777), [#778](https://github.com/optimizely/javascript-sdk/pull/778), [#786](https://github.com/optimizely/javascript-sdk/pull/786), [#789](https://github.com/optimizely/javascript-sdk/pull/789), [#790](https://github.com/optimizely/javascript-sdk/pull/790), [#797](https://github.com/optimizely/javascript-sdk/pull/797), [#799](https://github.com/optimizely/javascript-sdk/pull/799), [#808](https://github.com/optimizely/javascript-sdk/pull/808)). + +You can use ODP, a high-performance [Customer Data Platform (CDP)]( https://www.optimizely.com/optimization-glossary/customer-data-platform/), to easily create complex real-time segments (RTS) using first-party and 50+ third-party data sources out of the box. You can create custom schemas that support the user attributes important for your business, and stitch together user behavior done on different devices to better understand and target your customers for personalized user experiences. ODP can be used as a single source of truth for these segments in any Optimizely or 3rd party tool. + +With ODP accounts integrated into Optimizely projects, you can build audiences using segments pre-defined in ODP. The SDK will fetch the segments for given users and make decisions using the segments. For access to ODP audience targeting in your Feature Experimentation account, please contact your Customer Success Manager. + +This version includes the following changes: + +- New API added to `OptimizelyUserContext`: + + - `fetchQualifiedSegments()`: this API will retrieve user segments from the ODP server. The fetched segments will be used for audience evaluation. The fetched data will be stored in the local cache to avoid repeated network delays. + + - When an `OptimizelyUserContext` is created, the SDK will automatically send an identify request to the ODP server to facilitate observing user activities. + +- New APIs added to `OptimizelyClient`: + + - `sendOdpEvent()`: customers can build/send arbitrary ODP events that will bind user identifiers and data to user profiles in ODP. + + - `createUserContext()` with anonymous user IDs: user-contexts can be created without a userId. The SDK will create and use a persistent `VUID` specific to a device when userId is not provided. + +For details, refer to our documentation pages: + +- [Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) + +- [Client SDK Support](https://docs.developers.optimizely.com/feature-experimentation/v1.0/docs/advanced-audience-targeting-for-client-side-sdks) + +- [Initialize JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/initialize-sdk-javascript-aat) + +- [OptimizelyUserContext JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizelyusercontext-javascript-aat) + +- [Advanced Audience Targeting segment qualification methods](https://docs.developers.optimizely.com/feature-experimentation/docs/advanced-audience-targeting-segment-qualification-methods-javascript) + +- [Send Optimizely Data Platform data using Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/send-odp-data-using-advanced-audience-targeting-javascript) + +Additionally, a handful of major package updates are also included in this release including `murmurhash`, `uuid`, and others. For more information, check out the **Breaking Changes** section below. ([#762](https://github.com/optimizely/javascript-sdk/pull/762)) + +### Breaking Changes +- `ODPManager` in the SDK is enabled by default. Unless an ODP account is integrated into the Optimizely projects, most `ODPManager` functions will be ignored. If needed, `ODPManager` can be disabled when `OptimizelyClient` is instantiated. - Updated `murmurhash` dependency to version `2.0.1`. - Updated `uuid` dependency to version `8.3.2`. - Dropped support for the following browser versions. @@ -19,6 +61,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Safari versions earlier than `13.0`. - Dropped support for Node JS versions earlier than `14`. +## Changed +- Updated `createUserContext`'s `userId` parameter to be optional due to the Browser variation's use of the new `vuid` field. Note: The Node variation of the SDK does **not** use the new `vuid` field and you should pass in a `userId` when within the context of the Node variant. + ## [4.9.3] - March 17, 2023 ### Changed diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index 70d44ef5d..547ee3d16 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -187,7 +187,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.3'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta'); }); it('should set the JavaScript client engine and version', function() { @@ -1055,9 +1055,9 @@ describe('javascript-sdk (Browser)', function() { const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); sinon.spy(apiManager, 'sendEvents'); const eventManager = new BrowserOdpEventManager({ - odpConfig, - apiManager, - logger, + odpConfig, + apiManager, + logger, }); const datafile = testData.getOdpIntegratedConfigWithSegments(); const client = optimizelyFactory.createInstance({ @@ -1078,7 +1078,7 @@ describe('javascript-sdk (Browser)', function() { clock.tick(100); - const [,,events] = apiManager.sendEvents.getCall(0).args; + const [, , events] = apiManager.sendEvents.getCall(0).args; const [firstEvent] = events; assert.equal(firstEvent.action, 'client_initialized'); assert.equal(firstEvent.type, 'fullstack'); diff --git a/packages/optimizely-sdk/lib/index.lite.tests.js b/packages/optimizely-sdk/lib/index.lite.tests.js index 509093b40..40cd7eb90 100644 --- a/packages/optimizely-sdk/lib/index.lite.tests.js +++ b/packages/optimizely-sdk/lib/index.lite.tests.js @@ -22,9 +22,9 @@ import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.lite'; import configValidator from './utils/config_validator'; -describe('optimizelyFactory', function () { - describe('APIs', function () { - it('should expose logger, errorHandler, eventDispatcher and enums', function () { +describe('optimizelyFactory', function() { + describe('APIs', function() { + it('should expose logger, errorHandler, eventDispatcher and enums', function() { assert.isDefined(optimizelyFactory.logging); assert.isDefined(optimizelyFactory.logging.createLogger); assert.isDefined(optimizelyFactory.logging.createNoOpLogger); @@ -33,39 +33,39 @@ describe('optimizelyFactory', function () { assert.isDefined(optimizelyFactory.enums); }); - describe('createInstance', function () { - var fakeErrorHandler = { handleError: function () { } }; - var fakeEventDispatcher = { dispatchEvent: function () { } }; + describe('createInstance', function() { + var fakeErrorHandler = { handleError: function() {} }; + var fakeEventDispatcher = { dispatchEvent: function() {} }; var fakeLogger; - beforeEach(function () { + beforeEach(function() { fakeLogger = { log: sinon.spy(), setLogLevel: sinon.spy() }; sinon.stub(loggerPlugin, 'createLogger').returns(fakeLogger); sinon.stub(configValidator, 'validate'); sinon.stub(console, 'error'); }); - afterEach(function () { + afterEach(function() { loggerPlugin.createLogger.restore(); configValidator.validate.restore(); console.error.restore(); }); - it('should not throw if the provided config is not valid and log an error if logger is passed in', function () { + it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { configValidator.validate.throws(new Error('Invalid config or something')); var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); - assert.doesNotThrow(function () { + assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, logger: localLogger, }); // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); }); sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); }); - it('should create an instance of optimizely', function () { + it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, @@ -73,10 +73,10 @@ describe('optimizelyFactory', function () { logger: fakeLogger, }); // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.3'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta'); }); }); }); diff --git a/packages/optimizely-sdk/lib/index.node.tests.js b/packages/optimizely-sdk/lib/index.node.tests.js index 45530b67b..d7904d2cd 100644 --- a/packages/optimizely-sdk/lib/index.node.tests.js +++ b/packages/optimizely-sdk/lib/index.node.tests.js @@ -24,9 +24,9 @@ import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; -describe('optimizelyFactory', function () { - describe('APIs', function () { - it('should expose logger, errorHandler, eventDispatcher and enums', function () { +describe('optimizelyFactory', function() { + describe('APIs', function() { + it('should expose logger, errorHandler, eventDispatcher and enums', function() { assert.isDefined(optimizelyFactory.logging); assert.isDefined(optimizelyFactory.logging.createLogger); assert.isDefined(optimizelyFactory.logging.createNoOpLogger); @@ -35,51 +35,51 @@ describe('optimizelyFactory', function () { assert.isDefined(optimizelyFactory.enums); }); - describe('createInstance', function () { - var fakeErrorHandler = { handleError: function () { } }; - var fakeEventDispatcher = { dispatchEvent: function () { } }; + describe('createInstance', function() { + var fakeErrorHandler = { handleError: function() {} }; + var fakeEventDispatcher = { dispatchEvent: function() {} }; var fakeLogger; - beforeEach(function () { + beforeEach(function() { fakeLogger = { log: sinon.spy(), setLogLevel: sinon.spy() }; sinon.stub(loggerPlugin, 'createLogger').returns(fakeLogger); sinon.stub(configValidator, 'validate'); sinon.stub(console, 'error'); }); - afterEach(function () { + afterEach(function() { loggerPlugin.createLogger.restore(); configValidator.validate.restore(); console.error.restore(); }); - it('should not throw if the provided config is not valid and log an error if logger is passed in', function () { + it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { configValidator.validate.throws(new Error('Invalid config or something')); var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); - assert.doesNotThrow(function () { + assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, logger: localLogger, }); // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); }); sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); }); - it('should not throw if the provided config is not valid and log an error if no logger is provided', function () { + it('should not throw if the provided config is not valid and log an error if no logger is provided', function() { configValidator.validate.throws(new Error('Invalid config or something')); - assert.doesNotThrow(function () { + assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, }); // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); }); sinon.assert.calledOnce(console.error); }); - it('should create an instance of optimizely', function () { + it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, @@ -87,23 +87,23 @@ describe('optimizelyFactory', function () { logger: fakeLogger, }); // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); + optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.3'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta'); }); - describe('event processor configuration', function () { + describe('event processor configuration', function() { var eventProcessorSpy; - beforeEach(function () { + beforeEach(function() { eventProcessorSpy = sinon.stub(eventProcessor, 'createEventProcessor').callThrough(); }); - afterEach(function () { + afterEach(function() { eventProcessor.createEventProcessor.restore(); }); - it('should ignore invalid event flush interval and use default instead', function () { + it('should ignore invalid event flush interval and use default instead', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -119,7 +119,7 @@ describe('optimizelyFactory', function () { ); }); - it('should use default event flush interval when none is provided', function () { + it('should use default event flush interval when none is provided', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -134,7 +134,7 @@ describe('optimizelyFactory', function () { ); }); - it('should use provided event flush interval when valid', function () { + it('should use provided event flush interval when valid', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -150,7 +150,7 @@ describe('optimizelyFactory', function () { ); }); - it('should ignore invalid event batch size and use default instead', function () { + it('should ignore invalid event batch size and use default instead', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -166,7 +166,7 @@ describe('optimizelyFactory', function () { ); }); - it('should use default event batch size when none is provided', function () { + it('should use default event batch size when none is provided', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, @@ -181,7 +181,7 @@ describe('optimizelyFactory', function () { ); }); - it('should use provided event batch size when valid', function () { + it('should use provided event batch size when valid', function() { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index 56f3ca965..af68dacc6 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -223,8 +223,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '4.9.3'; -export const NODE_CLIENT_VERSION = '4.9.3'; +export const BROWSER_CLIENT_VERSION = '5.0.0-beta'; +export const NODE_CLIENT_VERSION = '5.0.0-beta'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json index 452984338..4f08c5eb0 100644 --- a/packages/optimizely-sdk/package-lock.json +++ b/packages/optimizely-sdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "4.9.3", + "version": "5.0.0-beta", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index 792677749..1d3de606f 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "4.9.3", + "version": "5.0.0-beta", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/packages/optimizely-sdk/tests/index.react_native.spec.ts b/packages/optimizely-sdk/tests/index.react_native.spec.ts index 311ca9080..3b7662c05 100644 --- a/packages/optimizely-sdk/tests/index.react_native.spec.ts +++ b/packages/optimizely-sdk/tests/index.react_native.spec.ts @@ -45,8 +45,8 @@ describe('javascript-sdk/react-native', () => { }); describe('createInstance', () => { - var fakeErrorHandler = { handleError: function () {} }; - var fakeEventDispatcher = { dispatchEvent: function () {} }; + var fakeErrorHandler = { handleError: function() {} }; + var fakeEventDispatcher = { dispatchEvent: function() {} }; // @ts-ignore var silentLogger; @@ -64,7 +64,7 @@ describe('javascript-sdk/react-native', () => { }); it('should not throw if the provided config is not valid', () => { - expect(function () { + expect(function() { var optlyInstance = optimizelyFactory.createInstance({ datafile: {}, // @ts-ignore @@ -72,7 +72,7 @@ describe('javascript-sdk/react-native', () => { }); // Invalid datafile causes onReady Promise rejection - catch this error // @ts-ignore - optlyInstance.onReady().catch(function () {}); + optlyInstance.onReady().catch(function() {}); }).not.toThrow(); }); @@ -86,11 +86,11 @@ describe('javascript-sdk/react-native', () => { }); // Invalid datafile causes onReady Promise rejection - catch this error // @ts-ignore - optlyInstance.onReady().catch(function () {}); + optlyInstance.onReady().catch(function() {}); expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('4.9.3'); + expect(optlyInstance.clientVersion).toEqual('5.0.0-beta'); }); it('should set the React Native JS client engine and javascript SDK version', () => { @@ -103,7 +103,7 @@ describe('javascript-sdk/react-native', () => { }); // Invalid datafile causes onReady Promise rejection - catch this error // @ts-ignore - optlyInstance.onReady().catch(function () {}); + optlyInstance.onReady().catch(function() {}); // @ts-ignore expect('react-native-js-sdk').toEqual(optlyInstance.clientEngine); // @ts-ignore @@ -121,7 +121,7 @@ describe('javascript-sdk/react-native', () => { }); // Invalid datafile causes onReady Promise rejection - catch this error // @ts-ignore - optlyInstance.onReady().catch(function () {}); + optlyInstance.onReady().catch(function() {}); // @ts-ignore expect('react-native-sdk').toEqual(optlyInstance.clientEngine); }); @@ -154,41 +154,67 @@ describe('javascript-sdk/react-native', () => { jest.resetAllMocks(); }); - it( - 'should call logging.setLogHandler with the supplied logger', - () => { - var fakeLogger = { log: function () { } }; - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - // @ts-ignore - logger: fakeLogger, - }); - expect(logging.setLogHandler).toBeCalledTimes(1); - expect(logging.setLogHandler).toBeCalledWith(fakeLogger); - } + it('should call logging.setLogHandler with the supplied logger', () => { + var fakeLogger = { log: function() {} }; + optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfig(), + // @ts-ignore + logger: fakeLogger, + }); + expect(logging.setLogHandler).toBeCalledTimes(1); + expect(logging.setLogHandler).toBeCalledWith(fakeLogger); + }); + }); + + describe('event processor configuration', () => { + // @ts-ignore + var eventProcessorSpy; + beforeEach(() => { + eventProcessorSpy = jest.spyOn(eventProcessor, 'createEventProcessor'); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should use default event flush interval when none is provided', () => { + optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + // @ts-ignore + logger: silentLogger, + }); + + expect( + // @ts-ignore + eventProcessorSpy + ).toBeCalledWith( + expect.objectContaining({ + flushInterval: 1000, + }) ); }); - describe('event processor configuration', () => { - // @ts-ignore - var eventProcessorSpy; + describe('with an invalid flush interval', () => { beforeEach(() => { - eventProcessorSpy = jest.spyOn(eventProcessor, 'createEventProcessor'); + jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); }); afterEach(() => { jest.resetAllMocks(); }); - it('should use default event flush interval when none is provided', () => { + it('should ignore the event flush interval and use the default instead', () => { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, + // @ts-ignore + eventFlushInterval: ['invalid', 'flush', 'interval'], }); - expect( // @ts-ignore eventProcessorSpy @@ -198,73 +224,73 @@ describe('javascript-sdk/react-native', () => { }) ); }); + }); - describe('with an invalid flush interval', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); - }); + describe('with a valid flush interval', () => { + beforeEach(() => { + jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); + }); - afterEach(() => { - jest.resetAllMocks(); - }); + afterEach(() => { + jest.resetAllMocks(); + }); - it('should ignore the event flush interval and use the default instead', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - // @ts-ignore - eventFlushInterval: ['invalid', 'flush', 'interval'], - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 1000, - }) - ); + it('should use the provided event flush interval', () => { + optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + // @ts-ignore + logger: silentLogger, + eventFlushInterval: 9000, }); + expect( + // @ts-ignore + eventProcessorSpy + ).toBeCalledWith( + expect.objectContaining({ + flushInterval: 9000, + }) + ); }); + }); - describe('with a valid flush interval', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); - }); + it('should use default event batch size when none is provided', () => { + optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + // @ts-ignore + logger: silentLogger, + }); + expect( + // @ts-ignore + eventProcessorSpy + ).toBeCalledWith( + expect.objectContaining({ + batchSize: 10, + }) + ); + }); - afterEach(() => { - jest.resetAllMocks(); - }); + describe('with an invalid event batch size', () => { + beforeEach(() => { + jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); + }); - it('should use the provided event flush interval', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - eventFlushInterval: 9000, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 9000, - }) - ); - }); + afterEach(() => { + jest.resetAllMocks(); }); - it('should use default event batch size when none is provided', () => { + it('should ignore the event batch size and use the default instead', () => { optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, + // @ts-ignore + eventBatchSize: null, }); expect( // @ts-ignore @@ -275,63 +301,34 @@ describe('javascript-sdk/react-native', () => { }) ); }); + }); - describe('with an invalid event batch size', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should ignore the event batch size and use the default instead', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - // @ts-ignore - eventBatchSize: null, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 10, - }) - ); - }); + describe('with a valid event batch size', () => { + beforeEach(() => { + jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); }); - describe('with a valid event batch size', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); - }); + afterEach(() => { + jest.resetAllMocks(); + }); - afterEach(() => { - jest.resetAllMocks(); + it('should use the provided event batch size', () => { + optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + // @ts-ignore + logger: silentLogger, + eventBatchSize: 300, }); - - it('should use the provided event batch size', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - eventBatchSize: 300, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 300, - }) - ); + expect( + // @ts-ignore + eventProcessorSpy + ).toBeCalledWith( + expect.objectContaining({ + batchSize: 300, + }) + ); }); }); }); From c1f0463e43169bb608ec462e1b88d2b67deae8c4 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Fri, 5 May 2023 12:30:26 -0400 Subject: [PATCH 015/200] [FSSDK-8950] change identifiers to be required for sendOdpEvent (#823) * Add event hasNecessaryIdentifiers check during enqueue * Add unit tests for verifying identifier per context * Fix requested PR changes * Change Map initialization --- .../lib/core/odp/odp_event_manager.ts | 7 ++ .../odp/event_manager/index.browser.ts | 3 + .../plugins/odp/event_manager/index.node.ts | 2 + .../tests/odpEventManager.spec.ts | 84 +++++++++++++++---- 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 8f90f73d0..3e1921c76 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -243,6 +243,11 @@ export abstract class OdpEventManager implements IOdpEventManager { return; } + if (!this.hasNecessaryIdentifiers(event)) { + this.logger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.'); + return; + } + if (this.queue.length >= this.queueSize) { this.logger.log( LogLevel.WARNING, @@ -257,6 +262,8 @@ export abstract class OdpEventManager implements IOdpEventManager { this.processQueue(); } + protected abstract hasNecessaryIdentifiers(event: OdpEvent): boolean; + /** * Process events in the main queue * @param shouldFlush Flush all events regardless of available queue event count diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts index 8bc5f23a4..fb6c51944 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts @@ -1,5 +1,6 @@ import { IOdpEventManager, OdpEventManager } from '../../../../lib/core/odp/odp_event_manager'; import { LogLevel } from '../../../modules/logging'; +import { OdpEvent } from "../../../core/odp/odp_event"; const DEFAULT_BROWSER_QUEUE_SIZE = 100; @@ -28,4 +29,6 @@ export class BrowserOdpEventManager extends OdpEventManager implements IOdpEvent // in Browser/client-side context, give debug message but leave events in queue this.getLogger().log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); } + + protected hasNecessaryIdentifiers = (event: OdpEvent): boolean => event.identifiers.size >= 0; } diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts index 3718a6f1c..8d3aa4a8f 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts @@ -29,4 +29,6 @@ export class NodeOdpEventManager extends OdpEventManager implements IOdpEventMan this.getLogger().log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); this.queue = new Array(); } + + protected hasNecessaryIdentifiers = (event: OdpEvent): boolean => event.identifiers.size >= 1; } diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index 992757602..de014698a 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE } from './../lib/utils/enums/index'; - +import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE } from '../lib/utils/enums'; import { OdpConfig } from '../lib/core/odp/odp_config'; import { STATE } from '../lib/core/odp/odp_event_manager'; -import { NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; +import { BrowserOdpEventManager } from "../lib/plugins/odp/event_manager/index.browser"; +import { NodeOdpEventManager, NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; import { LogHandler, LogLevel } from '../lib/modules/logging'; @@ -33,14 +33,12 @@ const EVENTS: OdpEvent[] = [ 't1', 'a1', new Map([['id-key-1', 'id-value-1']]), - new Map( - Object.entries({ - 'key-1': 'value1', - 'key-2': null, - 'key-3': 3.3, - 'key-4': true, - }) - ) + new Map([ + ['key-1', 'value1'], + ['key-2', null], + ['key-3', 3.3], + ['key-4', true], + ]), ), new OdpEvent( 't2', @@ -90,6 +88,26 @@ const PROCESSED_EVENTS: OdpEvent[] = [ ) ), ]; +const EVENT_WITH_EMPTY_IDENTIFIER = new OdpEvent( + 't4', + 'a4', + new Map(), + new Map([ + ['key-53f3', true], + ['key-a04a', 123], + ['key-2ab4', 'Linus Torvalds'], + ]), +); +const EVENT_WITH_UNDEFINED_IDENTIFIER = new OdpEvent( + 't4', + 'a4', + undefined, + new Map([ + ['key-53f3', false], + ['key-a04a', 456], + ['key-2ab4', 'Bill Gates'] + ]), +); const makeEvent = (id: number) => { const identifiers = new Map(); identifiers.set('identifier1', 'value1-' + id); @@ -185,12 +203,10 @@ describe('OdpEventManager', () => { 't3', 'a3', new Map([['id-key-3', 'id-value-3']]), - new Map( - Object.entries({ - 'key-1': false, - 'key-2': { random: 'object', whichShouldFail: true }, - }) - ) + new Map([ + ['key-1', false], + ['key-2', { random: 'object', whichShouldFail: true }], + ]), ); eventManager.sendEvent(badEvent); @@ -455,4 +471,38 @@ describe('OdpEventManager', () => { expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[0]); expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[1]); }); + + it('should error when no identifiers are provided in Node', () => { + const eventManager = new NodeOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + }); + + eventManager.start(); + eventManager.sendEvent(EVENT_WITH_EMPTY_IDENTIFIER); + eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); + eventManager.stop(); + + verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).twice(); + }); + + it('should never error when no identifiers are provided in Browser', () => { + const eventManager = new BrowserOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + }); + + eventManager.start(); + eventManager.sendEvent(EVENT_WITH_EMPTY_IDENTIFIER); + eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); + eventManager.stop(); + + verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).never(); + }); }); From 4b5b7f41f10034a521ca46f3263c525da550a860 Mon Sep 17 00:00:00 2001 From: raju-opti <129887101+raju-opti@users.noreply.github.com> Date: Wed, 24 May 2023 17:04:27 +0600 Subject: [PATCH 016/200] [FSSDK-9369] fix: fix makeRequest method of NodeRequestHandler class (#826) --- .../node_request_handler.ts | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts b/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts index 3de55a9e7..364467511 100644 --- a/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts +++ b/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import http from 'http'; import https from 'https'; import url from 'url'; @@ -62,17 +61,14 @@ export class NodeRequestHandler implements RequestHandler { }, timeout: this.timeout, }); - const responsePromise = this.getResponseFromRequest(request); + const abortableRequest = this.getAbortableRequestFromRequest(request); if (data) { request.write(data); } request.end(); - return { - abort: () => request.destroy(), - responsePromise, - }; + return abortableRequest; } /** @@ -119,11 +115,19 @@ export class NodeRequestHandler implements RequestHandler { * Sends a built request handling response, errors, and events around the transmission * @param request Request to send * @private - * @returns Response Promise-wrapped, simplified response object + * @returns AbortableRequest with simplified response promise */ - private getResponseFromRequest(request: http.ClientRequest): Promise { - return new Promise((resolve, reject) => { + private getAbortableRequestFromRequest(request: http.ClientRequest): AbortableRequest { + let aborted = false; + + const abort = () => { + aborted = true; + request.destroy(); + }; + + const responsePromise: Promise = new Promise((resolve, reject) => { request.on('timeout', () => { + aborted = true; request.destroy(); reject(new Error('Request timed out')); }); @@ -140,7 +144,7 @@ export class NodeRequestHandler implements RequestHandler { }); request.once('response', (incomingMessage: http.IncomingMessage) => { - if (request.destroyed) { + if (aborted) { return; } @@ -150,13 +154,13 @@ export class NodeRequestHandler implements RequestHandler { let responseData = ''; response.on('data', (chunk: string) => { - if (!request.destroyed) { + if (!aborted) { responseData += chunk; } }); response.on('end', () => { - if (request.destroyed) { + if (aborted) { return; } @@ -168,5 +172,7 @@ export class NodeRequestHandler implements RequestHandler { }); }); }); + + return { abort, responsePromise }; } } From b5e6d99023b9bd3f6f314869491a8a0b0255177b Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Wed, 24 May 2023 11:54:40 -0400 Subject: [PATCH 017/200] [FSSDK-9032] enhancement: Add VUID instantiation to browser client promise dependencies (#825) --- .../optimizely-sdk/lib/optimizely/index.ts | 32 ++++--- .../odp/event_api_manager/index.browser.ts | 26 +++--- .../odp/event_api_manager/index.node.ts | 18 ++-- .../optimizely-sdk/lib/utils/fns/index.ts | 85 +++++++++++-------- 4 files changed, 89 insertions(+), 72 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 006f88c98..2300546f4 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -15,7 +15,7 @@ ***************************************************************************/ import { LoggerFacade, ErrorHandler } from '../modules/logging'; -import { sprintf, objectValues } from '../utils/fns'; +import { sprintf, objectValues, isBrowser } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; import { EventProcessor } from '../modules/event_processor'; @@ -69,6 +69,8 @@ import { ODP_USER_KEY, } from '../utils/enums'; +import { BrowserOdpManager } from '../../lib/plugins/odp_manager/index.browser'; + const MODULE_NAME = 'OPTIMIZELY'; const DEFAULT_ONREADY_TIMEOUT = 30000; @@ -175,24 +177,20 @@ export default class Optimizely implements Client { const eventProcessorStartedPromise = this.eventProcessor.start(); - // TODO: Look into making VUID Initialization a dependent promise for Browser Contexts - - // let dependentPromises: Promise[] = [projectConfigManagerReadyPromise]; - - // if (isBrowserContext()) { - // const odpManagerVuidInitializedPromise = (config.odpManager as BrowserOdpManager).initPromise; - // if (odpManagerVuidInitializedPromise) { - // dependentPromises.push(odpManagerVuidInitializedPromise); - // } - // } - - // this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { - - this.readyPromise = Promise.all([ + const dependentPromises: Array | void> = [ projectConfigManagerReadyPromise, eventProcessorStartedPromise, - // config.odpManager.initPromise, - ]).then(promiseResults => { + ]; + + // Adds VUID initialization promise as a dependency for Browser... + if (isBrowser()) { + const odpManagerVuidInitializedPromise = (config.odpManager as BrowserOdpManager).initPromise; + if (odpManagerVuidInitializedPromise) { + dependentPromises.push(odpManagerVuidInitializedPromise); + } + } + + this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { if (config.odpManager != null) { this.odpManager = config.odpManager; this.odpManager.eventManager?.start(); diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts index ab561c664..f01e82b24 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts @@ -1,6 +1,6 @@ -import { OdpEvent } from "../../../core/odp/odp_event"; -import { OdpEventApiManager } from "../../../core/odp/odp_event_api_manager"; -import { LogHandler, LogLevel } from '../../../modules/logging'; +import { OdpEvent } from '../../../core/odp/odp_event'; +import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; +import { LogLevel } from '../../../modules/logging'; import { ODP_EVENT_BROWSER_ENDPOINT } from '../../../utils/enums'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; @@ -14,15 +14,19 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { return false; } - protected generateRequestData(apiHost: string, apiKey: string, events: OdpEvent[]): { method: string; endpoint: string; headers: { [key: string]: string; }; data: string; } { - const method = 'GET'; + protected generateRequestData( + apiHost: string, + apiKey: string, + events: OdpEvent[] + ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { + const method = 'GET'; const event = events[0]; const url = new URL(ODP_EVENT_BROWSER_ENDPOINT); - event.identifiers.forEach((v, k) =>{ - url.searchParams.append(k, v); + event.identifiers.forEach((v, k) => { + url.searchParams.append(k, v); }); - event.data.forEach((v, k) =>{ - url.searchParams.append(k, v as string); + event.data.forEach((v, k) => { + url.searchParams.append(k, v as string); }); url.searchParams.append('tracker_id', apiKey); url.searchParams.append('event_type', event.type); @@ -32,7 +36,7 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { method, endpoint, headers: {}, - data: "", + data: '', }; } -} \ No newline at end of file +} diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts index 06dc79e8d..f61f33d10 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts @@ -1,21 +1,25 @@ -import { OdpEvent } from "../../../core/odp/odp_event"; -import { OdpEventApiManager } from "../../../core/odp/odp_event_api_manager"; +import { OdpEvent } from '../../../core/odp/odp_event'; +import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; export class NodeOdpEventApiManager extends OdpEventApiManager { protected shouldSendEvents(events: OdpEvent[]): boolean { return true; } - protected generateRequestData(apiHost: string, apiKey: string, events: OdpEvent[]): { method: string; endpoint: string; headers: { [key: string]: string; }; data: string; } { + protected generateRequestData( + apiHost: string, + apiKey: string, + events: OdpEvent[] + ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { return { method: 'POST', endpoint: `${apiHost}/v3/events`, headers: { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, + 'Content-Type': 'application/json', + 'x-api-key': apiKey, }, data: JSON.stringify(events, this.replacer), - } + }; } private replacer(_: unknown, value: unknown) { @@ -25,4 +29,4 @@ export class NodeOdpEventApiManager extends OdpEventApiManager { return value; } } -} \ No newline at end of file +} diff --git a/packages/optimizely-sdk/lib/utils/fns/index.ts b/packages/optimizely-sdk/lib/utils/fns/index.ts index 6852e030d..157fb4335 100644 --- a/packages/optimizely-sdk/lib/utils/fns/index.ts +++ b/packages/optimizely-sdk/lib/utils/fns/index.ts @@ -51,7 +51,7 @@ function isSafeInteger(number: unknown): boolean { export function keyBy(arr: K[], key: string): { [key: string]: K } { if (!arr) return {}; - return keyByUtil(arr, function (item) { + return keyByUtil(arr, function(item) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (item as any)[key]; }); @@ -62,84 +62,84 @@ function isNumber(value: unknown): boolean { } export function uuid(): string { - return v4() + return v4(); } -export type Omit = Pick> +export type Omit = Pick>; export function getTimestamp(): number { - return new Date().getTime() + return new Date().getTime(); } /** -* Validates a value is a valid TypeScript enum -* -* @export -* @param {object} enumToCheck -* @param {*} value -* @returns {boolean} -*/ + * Validates a value is a valid TypeScript enum + * + * @export + * @param {object} enumToCheck + * @param {*} value + * @returns {boolean} + */ // TODO[OASIS-6649]: Don't use any type // eslint-disable-next-line @typescript-eslint/no-explicit-any export function isValidEnum(enumToCheck: { [key: string]: any }, value: number | string): boolean { - let found = false + let found = false; - const keys = Object.keys(enumToCheck) + const keys = Object.keys(enumToCheck); for (let index = 0; index < keys.length; index++) { if (value === enumToCheck[keys[index]]) { - found = true - break + found = true; + break; } } - return found + return found; } export function groupBy(arr: K[], grouperFn: (item: K) => string): Array { - const grouper: { [key: string]: K[] } = {} + const grouper: { [key: string]: K[] } = {}; arr.forEach(item => { - const key = grouperFn(item) - grouper[key] = grouper[key] || [] - grouper[key].push(item) - }) + const key = grouperFn(item); + grouper[key] = grouper[key] || []; + grouper[key].push(item); + }); - return objectValues(grouper) + return objectValues(grouper); } export function objectValues(obj: { [key: string]: K }): K[] { - return Object.keys(obj).map(key => obj[key]) + return Object.keys(obj).map(key => obj[key]); } export function objectEntries(obj: { [key: string]: K }): [string, K][] { - return Object.keys(obj).map(key => [key, obj[key]]) + return Object.keys(obj).map(key => [key, obj[key]]); } export function find(arr: K[], cond: (arg: K) => boolean): K | undefined { - let found + let found; for (const item of arr) { if (cond(item)) { - found = item - break + found = item; + break; } } - return found + return found; } export function keyByUtil(arr: K[], keyByFn: (item: K) => string): { [key: string]: K } { - const map: { [key: string]: K } = {} + const map: { [key: string]: K } = {}; arr.forEach(item => { - const key = keyByFn(item) - map[key] = item - }) - return map + const key = keyByFn(item); + map[key] = item; + }); + return map; } // TODO[OASIS-6649]: Don't use any type // eslint-disable-next-line @typescript-eslint/no-explicit-any export function sprintf(format: string, ...args: any[]): string { - let i = 0 + let i = 0; return format.replace(/%s/g, function() { const arg = args[i++]; const type = typeof arg; @@ -150,11 +150,9 @@ export function sprintf(format: string, ...args: any[]): string { } else { return String(arg); } - }) + }); } - - /** * Checks two string arrays for equality. * @param arrayA First Array to be compared against. @@ -165,6 +163,18 @@ export function checkArrayEquality(arrayA: string[], arrayB: string[]): boolean return arrayA.length === arrayB.length && arrayA.every((item, index) => item === arrayB[index]); } +/** + * Checks the current running context + * @returns {boolean} True if window object exists. + */ +export function isBrowser(): boolean { + if (typeof window === 'object' && typeof process !== 'object' && typeof require !== 'function') { + return true; + } + + return false; +} + export default { assign, checkArrayEquality, @@ -181,4 +191,5 @@ export default { find, keyByUtil, sprintf, + isBrowser, }; From ac00afce497228b34ec9e680a7f0dcf412735095 Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Tue, 30 May 2023 14:25:38 -0400 Subject: [PATCH 018/200] [FSSDK-9362] chore: odp cleanup + adjust odpmanager initpromise implementation (#827) --- .../notification_registry.ts | 7 +-- packages/optimizely-sdk/lib/core/odp/index.ts | 15 ------- .../optimizely-sdk/lib/core/odp/odp_config.ts | 12 +++--- .../optimizely-sdk/lib/core/odp/odp_event.ts | 10 ++--- .../lib/core/odp/odp_event_api_manager.ts | 29 +++++++++---- .../lib/core/odp/odp_event_manager.ts | 30 ++++++++----- .../lib/core/odp/odp_manager.ts | 43 +++++++++++++++++-- .../lib/core/odp/odp_segment_api_manager.ts | 6 +-- .../lib/core/odp/odp_segment_manager.ts | 4 +- .../optimizely-sdk/lib/index.browser.tests.js | 33 ++++++++++++++ .../optimizely-sdk/lib/optimizely/index.ts | 12 ++---- .../lib/plugins/odp_manager/index.browser.ts | 9 ++-- .../lib/plugins/odp_manager/index.node.ts | 2 + .../lib/plugins/vuid_manager/index.ts | 8 +++- .../optimizely-sdk/lib/utils/fns/index.ts | 15 +------ .../node_request_handler.ts | 9 ++-- .../lib/utils/lru_cache/lru_cache.ts | 8 ++-- .../tests/odpManager.browser.spec.ts | 1 - .../tests/odpSegmentManager.spec.ts | 2 +- 19 files changed, 158 insertions(+), 97 deletions(-) delete mode 100644 packages/optimizely-sdk/lib/core/odp/index.ts diff --git a/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts b/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts index 80f9eb9f6..12fe1178e 100644 --- a/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts +++ b/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts @@ -31,10 +31,7 @@ export class NotificationRegistry { * @param logger Logger to be used for the corresponding notification center * @returns {NotificationCenter | undefined} a notification center instance for ODP Manager if a valid SDK Key is provided, otherwise undefined */ - public static getNotificationCenter( - sdkKey?: string, - logger: LogHandler = getLogger() - ): NotificationCenter | undefined { + static getNotificationCenter(sdkKey?: string, logger: LogHandler = getLogger()): NotificationCenter | undefined { if (!sdkKey) { logger.log(LogLevel.ERROR, 'No SDK key provided to getNotificationCenter.'); return undefined; @@ -54,7 +51,7 @@ export class NotificationRegistry { return notificationCenter; } - public static removeNotificationCenter(sdkKey?: string): void { + static removeNotificationCenter(sdkKey?: string): void { if (!sdkKey) { return; } diff --git a/packages/optimizely-sdk/lib/core/odp/index.ts b/packages/optimizely-sdk/lib/core/odp/index.ts deleted file mode 100644 index 77a5a49e1..000000000 --- a/packages/optimizely-sdk/lib/core/odp/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/core/odp/odp_config.ts b/packages/optimizely-sdk/lib/core/odp/odp_config.ts index 3bf407281..4fc7ae298 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_config.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_config.ts @@ -27,7 +27,7 @@ export class OdpConfig { * Getter to retrieve the ODP server host * @public */ - public get apiHost(): string { + get apiHost(): string { return this._apiHost; } @@ -41,7 +41,7 @@ export class OdpConfig { * Getter to retrieve the ODP API key * @public */ - public get apiKey(): string { + get apiKey(): string { return this._apiKey; } @@ -55,7 +55,7 @@ export class OdpConfig { * Getter for ODP segments to check * @public */ - public get segmentsToCheck(): string[] { + get segmentsToCheck(): string[] { return this._segmentsToCheck; } @@ -70,7 +70,7 @@ export class OdpConfig { * @param {OdpConfig} config New ODP Config to potentially update self with * @returns true if configuration was updated successfully */ - public update(config: OdpConfig): boolean { + update(config: OdpConfig): boolean { if (this.equals(config)) { return false; } else { @@ -85,7 +85,7 @@ export class OdpConfig { /** * Determines if ODP configuration has the minimum amount of information */ - public isReady(): boolean { + isReady(): boolean { return !!this._apiKey && !!this._apiHost; } @@ -94,7 +94,7 @@ export class OdpConfig { * @param configToCompare ODP Configuration to check self against for equality * @returns Boolean based on if the current ODP Config is equivalent to the incoming ODP Config */ - public equals(configToCompare: OdpConfig): boolean { + equals(configToCompare: OdpConfig): boolean { return ( this._apiHost === configToCompare._apiHost && this._apiKey === configToCompare._apiKey && diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event.ts b/packages/optimizely-sdk/lib/core/odp/odp_event.ts index 4260cd30d..e777789bc 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,22 +18,22 @@ export class OdpEvent { /** * Type of event (typically "fullstack") */ - public type: string; + type: string; /** * Subcategory of the event type */ - public action: string; + action: string; /** * Key-value map of user identifiers */ - public identifiers: Map; + identifiers: Map; /** * Event data in a key-value map */ - public data: Map; + data: Map; /** * Event to be sent and stored in the Optimizely Data Platform diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts index d067855e1..09fcfd230 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts @@ -31,7 +31,16 @@ export interface IOdpEventApiManager { * Concrete implementation for accessing the ODP REST API */ export abstract class OdpEventApiManager implements IOdpEventApiManager { + /** + * Handler for recording execution logs + * @private + */ private readonly logger: LogHandler; + + /** + * Handler for making external HTTP/S requests + * @private + */ private readonly requestHandler: RequestHandler; /** @@ -44,7 +53,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { this.logger = logger; } - public getLogger(): LogHandler { + getLogger(): LogHandler { return this.logger; } @@ -55,7 +64,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { * @param events ODP events to send * @returns Retry is true - if network or server error (5xx), otherwise false */ - public async sendEvents(apiKey: string, apiHost: string, events: OdpEvent[]): Promise { + async sendEvents(apiKey: string, apiHost: string, events: OdpEvent[]): Promise { let shouldRetry = false; if (!apiKey || !apiHost) { @@ -101,10 +110,14 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { protected abstract shouldSendEvents(events: OdpEvent[]): boolean; - protected abstract generateRequestData(apiHost: string, apiKey: string, events: OdpEvent[]): { - method: string, - endpoint: string, - headers: {[key: string]: string}, - data: string, - } + protected abstract generateRequestData( + apiHost: string, + apiKey: string, + events: OdpEvent[] + ): { + method: string; + endpoint: string; + headers: { [key: string]: string }; + data: string; + }; } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index 3e1921c76..ad444780f 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -61,59 +61,69 @@ export abstract class OdpEventManager implements IOdpEventManager { /** * Current state of the event processor */ - public state: STATE = STATE.STOPPED; + state: STATE = STATE.STOPPED; + /** * Queue for holding all events to be eventually dispatched * @protected */ protected queue = new Array(); + /** * Identifier of the currently running timeout so clearCurrentTimeout() can be called * @private */ private timeoutId?: NodeJS.Timeout | number; + /** - * ODP configuration settings in used + * ODP configuration settings for identifying the target API and segments * @private */ private odpConfig: OdpConfig; + /** * REST API Manager used to send the events * @private */ private readonly apiManager: IOdpEventApiManager; + /** * Handler for recording execution logs * @private */ private readonly logger: LogHandler; + /** * Maximum queue size * @protected */ protected queueSize!: number; + /** * Maximum number of events to process at once. Ignored in browser context * @protected */ protected batchSize!: number; + /** * Milliseconds between setTimeout() to process new batches. Ignored in browser context * @protected */ protected flushInterval!: number; + /** * Type of execution context eg node, js, react * @private */ private readonly clientEngine: string; + /** * Version of the client being used * @private */ private readonly clientVersion: string; - public constructor({ + constructor({ odpConfig, apiManager, logger, @@ -151,21 +161,21 @@ export abstract class OdpEventManager implements IOdpEventManager { * Update ODP configuration settings. * @param newConfig New configuration to apply */ - public updateSettings(newConfig: OdpConfig): void { + updateSettings(newConfig: OdpConfig): void { this.odpConfig = newConfig; } /** * Cleans up all pending events; occurs every time the ODP Config is updated. */ - public flush(): void { + flush(): void { this.processQueue(true); } /** * Start processing events in the queue */ - public start(): void { + start(): void { this.state = STATE.RUNNING; this.setNewTimeout(); @@ -174,7 +184,7 @@ export abstract class OdpEventManager implements IOdpEventManager { /** * Drain the queue sending all remaining events in batches then stop processing */ - public async stop(): Promise { + async stop(): Promise { this.logger.log(LogLevel.DEBUG, 'Stop requested.'); await this.processQueue(true); @@ -187,7 +197,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * Register a new visitor user id (VUID) in ODP * @param vuid Visitor User ID to send */ - public registerVuid(vuid: string): void { + registerVuid(vuid: string): void { const identifiers = new Map(); identifiers.set(ODP_USER_KEY.VUID, vuid); @@ -200,7 +210,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * @param {string} userId (Optional) Full-stack User ID * @param {string} vuid (Optional) Visitor User ID */ - public identifyUser(userId?: string, vuid?: string): void { + identifyUser(userId?: string, vuid?: string): void { const identifiers = new Map(); if (!userId && !vuid) { this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_UID_MISSING); @@ -223,7 +233,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * Send an event to ODP via dispatch queue * @param event ODP Event to forward */ - public sendEvent(event: OdpEvent): void { + sendEvent(event: OdpEvent): void { if (invalidOdpDataFound(event.data)) { this.logger.log(LogLevel.ERROR, 'Event data found to be invalid.'); } else { diff --git a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts index 961a86796..84aa630e2 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_manager.ts @@ -27,16 +27,31 @@ import { OptimizelySegmentOption } from './optimizely_segment_option'; import { invalidOdpDataFound } from './odp_utils'; import { OdpEvent } from './odp_event'; +/** + * Manager for handling internal all business logic related to + * Optimizely Data Platform (ODP) / Advanced Audience Targeting (AAT) + */ export interface IOdpManager { + initPromise?: Promise; + enabled: boolean; + segmentManager: IOdpSegmentManager | undefined; + eventManager: IOdpEventManager | undefined; + updateSettings({ apiKey, apiHost, segmentsToCheck }: OdpConfig): boolean; + close(): void; + fetchQualifiedSegments(userId: string, options?: Array): Promise; + identifyUser(userId?: string, vuid?: string): void; + sendEvent({ type, action, identifiers, data }: OdpEvent): void; + isVuidEnabled(): boolean; + getVuid(): string | undefined; } @@ -44,10 +59,15 @@ export interface IOdpManager { * Orchestrates segments manager, event manager, and ODP configuration */ export abstract class OdpManager implements IOdpManager { + /** + * Promise that returns when the OdpManager is finished initializing + */ initPromise?: Promise; + + /** + * Switch to enable/disable ODP Manager functionality + */ enabled = true; - logger: LogHandler = getLogger(); - odpConfig: OdpConfig = new OdpConfig(); /** * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. @@ -61,7 +81,18 @@ export abstract class OdpManager implements IOdpManager { */ eventManager: IOdpEventManager | undefined; - constructor() {} + /** + * Handler for recording execution logs + * @protected + */ + protected logger: LogHandler = getLogger(); // TODO: Consider making private and moving instantiation to constructor + + /** + * ODP configuration settings for identifying the target API and segments + */ + odpConfig: OdpConfig = new OdpConfig(); // TODO: Consider making private and adding public accessors + + constructor() {} // TODO: Consider accepting logger as a parameter and initializing it in constructor instead /** * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments @@ -195,7 +226,13 @@ export abstract class OdpManager implements IOdpManager { this.eventManager.sendEvent(new OdpEvent(mType, action, identifiers, data)); } + /** + * Identifies if the VUID feature is enabled + */ abstract isVuidEnabled(): boolean; + /** + * Returns VUID value if it exists + */ abstract getVuid(): string | undefined; } diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts index 60835db4e..5978b3c6a 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { * @param userValue Associated value to query for the user key * @param segmentsToCheck Audience segments to check for experiment inclusion */ - public async fetchSegments( + async fetchSegments( apiKey: string, apiHost: string, userKey: ODP_USER_KEY, @@ -110,7 +110,7 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { if (parsedSegments.errors?.length > 0) { const { code, classification } = parsedSegments.errors[0].extensions; - if (code == "INVALID_IDENTIFIER_EXCEPTION") { + if (code == 'INVALID_IDENTIFIER_EXCEPTION') { this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (invalid identifier)`); } else { this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (${classification})`); diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts index a8c30d616..1d89fd467 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts @@ -52,7 +52,7 @@ export class OdpSegmentManager implements IOdpSegmentManager { * Getter for private segments cache * @public */ - public get segmentsCache(): ICache { + get segmentsCache(): ICache { return this._segmentsCache; } @@ -162,7 +162,7 @@ export class OdpSegmentManager implements IOdpSegmentManager { * Updates the ODP Config settings of ODP Segment Manager * @param config New ODP Config that will overwrite the existing config */ - public updateSettings(config: OdpConfig): void { + updateSettings(config: OdpConfig): void { this.odpConfig = config; this._segmentsCache.reset(); } diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index 547ee3d16..72d4d8635 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -25,6 +25,7 @@ import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import OptimizelyUserContext from './optimizely_user_context'; + import { LOG_MESSAGES, ODP_EVENT_ACTION, ODP_EVENT_BROWSER_ENDPOINT } from './utils/enums'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import { OdpConfig } from './core/odp/odp_config'; @@ -644,6 +645,38 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, LOG_MESSAGES.ODP_DISABLED); }); + it('should include the VUID instantation promise of Browser ODP Manager in the Optimizely client onReady promise dependency array', () => { + const client = optimizelyFactory.createInstance({ + datafile: testData.getTestProjectConfigWithFeatures(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpManager: new BrowserOdpManager({ + logger, + }), + }); + + client + .onReady() + .then(() => { + assert.isDefined(client.odpManager.initPromise); + client.odpManager.initPromise + .then(() => { + assert.isTrue(true); + }) + .catch(() => { + assert.isTrue(false); + }); + assert.isDefined(client.odpManager.getVuid()); + }) + .catch(() => { + assert.isTrue(false); + }); + + sinon.assert.neverCalledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.ERROR); + }); + it('should accept a valid custom cache size', () => { const client = optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfigWithFeatures(), diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 2300546f4..f7cdb696f 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -15,7 +15,7 @@ ***************************************************************************/ import { LoggerFacade, ErrorHandler } from '../modules/logging'; -import { sprintf, objectValues, isBrowser } from '../utils/fns'; +import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; import { EventProcessor } from '../modules/event_processor'; @@ -180,17 +180,11 @@ export default class Optimizely implements Client { const dependentPromises: Array | void> = [ projectConfigManagerReadyPromise, eventProcessorStartedPromise, + config.odpManager?.initPromise, ]; - // Adds VUID initialization promise as a dependency for Browser... - if (isBrowser()) { - const odpManagerVuidInitializedPromise = (config.odpManager as BrowserOdpManager).initPromise; - if (odpManagerVuidInitializedPromise) { - dependentPromises.push(odpManagerVuidInitializedPromise); - } - } - this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { + // If no odpManager exists yet, creates a new one if (config.odpManager != null) { this.odpManager = config.odpManager; this.odpManager.eventManager?.start(); diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts index 7a2784a02..d7c29c493 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts @@ -130,6 +130,7 @@ export class BrowserOdpManager extends OdpManager { /** * Upon initializing BrowserOdpManager, accesses or creates new VUID from Browser cache and registers it via the Event Manager + * @private */ private async initializeVuid(cache: PersistentKeyValueCache): Promise { const vuidManager = await VuidManager.instance(cache); @@ -156,7 +157,7 @@ export class BrowserOdpManager extends OdpManager { * - Additionally, also passes VUID to help identify client-side users * @param fsUserId Unique identifier of a target user. */ - public identifyUser(fsUserId?: string, vuid?: string): void { + identifyUser(fsUserId?: string, vuid?: string): void { if (fsUserId && VuidManager.isVuid(fsUserId)) { super.identifyUser(undefined, fsUserId); return; @@ -177,7 +178,7 @@ export class BrowserOdpManager extends OdpManager { * - Identifiers must contain at least one key-value pair * @param {OdpEvent} odpEvent > ODP Event to send to event manager */ - public sendEvent({ type, action, identifiers, data }: OdpEvent): void { + sendEvent({ type, action, identifiers, data }: OdpEvent): void { const identifiersWithVuid = new Map(identifiers); if (!identifiers.has(ODP_USER_KEY.VUID)) { @@ -191,11 +192,11 @@ export class BrowserOdpManager extends OdpManager { super.sendEvent({ type, action, identifiers: identifiersWithVuid, data }); } - public isVuidEnabled(): boolean { + isVuidEnabled(): boolean { return true; } - public getVuid(): string | undefined { + getVuid(): string | undefined { return this.vuid; } } diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts index 6f5324820..bee3bf19e 100644 --- a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts +++ b/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts @@ -115,6 +115,8 @@ export class NodeOdpManager extends OdpManager { } this.eventManager!.start(); + + this.initPromise = Promise.resolve(); } public isVuidEnabled(): boolean { diff --git a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts b/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts index 2b29ff9e4..8587724d6 100644 --- a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts +++ b/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts @@ -48,7 +48,7 @@ export class VuidManager implements IVuidManager { /** * Get the current VUID value being used */ - public get vuid(): string { + get vuid(): string { return this._vuid; } @@ -67,7 +67,7 @@ export class VuidManager implements IVuidManager { * @param cache Caching mechanism to use for persisting the VUID outside working memory * * @returns An instance of VuidManager */ - public static async instance(cache: PersistentKeyValueCache): Promise { + static async instance(cache: PersistentKeyValueCache): Promise { if (!this._instance) { this._instance = new VuidManager(); } @@ -83,6 +83,7 @@ export class VuidManager implements IVuidManager { * Attempts to load a VUID from persistent cache or generates a new VUID * @param cache Caching mechanism to use for persisting the VUID outside working memory * @returns Current VUID stored in the VuidManager + * @private */ private async load(cache: PersistentKeyValueCache): Promise { const cachedValue = await cache.get(this._keyForVuid); @@ -99,6 +100,7 @@ export class VuidManager implements IVuidManager { /** * Creates a new VUID * @returns A new visitor unique identifier + * @private */ private makeVuid(): string { const maxLength = 32; // required by ODP server @@ -115,6 +117,7 @@ export class VuidManager implements IVuidManager { * Saves a VUID to a persistent cache * @param vuid VUID to be stored * @param cache Caching mechanism to use for persisting the VUID outside working memory + * @private */ private async save(vuid: string, cache: PersistentKeyValueCache): Promise { await cache.set(this._keyForVuid, vuid); @@ -130,6 +133,7 @@ export class VuidManager implements IVuidManager { /** * Function used in unit testing to reset the VuidManager * **Important**: This should not to be used in production code + * @private */ private static _reset(): void { this._instance._vuid = ''; diff --git a/packages/optimizely-sdk/lib/utils/fns/index.ts b/packages/optimizely-sdk/lib/utils/fns/index.ts index 157fb4335..056278548 100644 --- a/packages/optimizely-sdk/lib/utils/fns/index.ts +++ b/packages/optimizely-sdk/lib/utils/fns/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2017, 2019-2020, 2022, Optimizely + * Copyright 2017, 2019-2020, 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -163,18 +163,6 @@ export function checkArrayEquality(arrayA: string[], arrayB: string[]): boolean return arrayA.length === arrayB.length && arrayA.every((item, index) => item === arrayB[index]); } -/** - * Checks the current running context - * @returns {boolean} True if window object exists. - */ -export function isBrowser(): boolean { - if (typeof window === 'object' && typeof process !== 'object' && typeof require !== 'function') { - return true; - } - - return false; -} - export default { assign, checkArrayEquality, @@ -191,5 +179,4 @@ export default { find, keyByUtil, sprintf, - isBrowser, }; diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts b/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts index 364467511..458089540 100644 --- a/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts +++ b/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts @@ -28,7 +28,7 @@ export class NodeRequestHandler implements RequestHandler { private readonly logger: LogHandler; private readonly timeout: number; - public constructor(logger: LogHandler, timeout: number = REQUEST_TIMEOUT_MS) { + constructor(logger: LogHandler, timeout: number = REQUEST_TIMEOUT_MS) { this.logger = logger; this.timeout = timeout; } @@ -41,14 +41,13 @@ export class NodeRequestHandler implements RequestHandler { * @param data? stringified version of data to POST, PUT, etc * @returns AbortableRequest contains both the response Promise and capability to abort() */ - public makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest { + makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest { const parsedUrl = url.parse(requestUrl); if (parsedUrl.protocol !== 'https:') { return { responsePromise: Promise.reject(new Error(`Unsupported protocol: ${parsedUrl.protocol}`)), - abort: () => { - }, + abort: () => {}, }; } @@ -119,7 +118,7 @@ export class NodeRequestHandler implements RequestHandler { */ private getAbortableRequestFromRequest(request: http.ClientRequest): AbortableRequest { let aborted = false; - + const abort = () => { aborted = true; request.destroy(); diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts index dae7f32df..0e8be1d8c 100644 --- a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts +++ b/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts @@ -65,7 +65,7 @@ export class LRUCache implements ICache { * Returns a valid, non-stale value from LRU Cache based on an input key. * Additionally moves the element to the end of the cache and removes from cache if stale. */ - public lookup(key: K): V | null { + lookup(key: K): V | null { if (this._maxSize <= 0) { return null; } @@ -89,7 +89,7 @@ export class LRUCache implements ICache { * Inserts/moves an input key-value pair to the end of the LRU Cache. * Removes the least-recently used element if the cache exceeds it's maxSize. */ - public save({ key, value }: { key: K; value: V }): void { + save({ key, value }: { key: K; value: V }): void { if (this._maxSize <= 0) return; const element: CacheElement | undefined = this._map.get(key); @@ -105,7 +105,7 @@ export class LRUCache implements ICache { /** * Clears the LRU Cache */ - public reset(): void { + reset(): void { if (this._maxSize <= 0) return; this._map.clear(); @@ -115,7 +115,7 @@ export class LRUCache implements ICache { * Reads value from specified key without moving elements in the LRU Cache. * @param {K} key */ - public peek(key: K): V | null { + peek(key: K): V | null { if (this._maxSize <= 0) return null; const element: CacheElement | undefined = this._map.get(key); diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts index b95226110..7f6a265d8 100644 --- a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts +++ b/packages/optimizely-sdk/tests/odpManager.browser.spec.ts @@ -31,7 +31,6 @@ import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.b import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; -import { OdpEvent } from '../lib/core/odp/odp_event'; import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; const keyA = 'key-a'; diff --git a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts index 8a0ec3382..d6e571dec 100644 --- a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts @@ -30,7 +30,7 @@ import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; describe('OdpSegmentManager', () => { class MockOdpSegmentApiManager extends OdpSegmentApiManager { - public async fetchSegments( + async fetchSegments( apiKey: string, apiHost: string, userKey: ODP_USER_KEY, From 5765e28d929caf85fbd7e9d479b291db8484f130 Mon Sep 17 00:00:00 2001 From: raju-opti <129887101+raju-opti@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:23:30 +0600 Subject: [PATCH 019/200] [FSSDK-9417] refactor: improve OptimizelyConfig instantiation performance (#828) --- .../lib/core/optimizely_config/index.ts | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/optimizely_config/index.ts b/packages/optimizely-sdk/lib/core/optimizely_config/index.ts index 968806094..8ea70ecce 100644 --- a/packages/optimizely-sdk/lib/core/optimizely_config/index.ts +++ b/packages/optimizely-sdk/lib/core/optimizely_config/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2021, Optimizely + * Copyright 2020-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,9 +74,15 @@ export class OptimizelyConfig { return resultMap; }, {}); - const experimentsMapById = OptimizelyConfig.getExperimentsMapById(configObj, featureIdVariablesMap); + const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj); + + const experimentsMapById = OptimizelyConfig.getExperimentsMapById( + configObj, featureIdVariablesMap, variableIdMap + ); this.experimentsMap = OptimizelyConfig.getExperimentsKeyMap(experimentsMapById); - this.featuresMap = OptimizelyConfig.getFeaturesMap(configObj, featureIdVariablesMap, experimentsMapById); + this.featuresMap = OptimizelyConfig.getFeaturesMap( + configObj, featureIdVariablesMap, experimentsMapById, variableIdMap + ); this.datafile = datafile; } @@ -242,7 +248,7 @@ export class OptimizelyConfig { * Gets Map of all experiment variations and variables including rollouts * @param {Variation[]} variations * @param {FeatureVariablesMap} featureIdVariableMap - * @param {[id: string]: FeatureVariable} variableIdMap + * @param {{[id: string]: FeatureVariable}} variableIdMap * @param {string} featureId * @returns {[key: string]: Variation} Variations mapped by key */ @@ -292,19 +298,20 @@ export class OptimizelyConfig { /** * Gets list of rollout experiments - * @param {ProjectConfig} configObj - * @param {FeatureVariablesMap} featureVariableIdMap - * @param {string} featureId - * @param {Experiment[]} experiments - * @returns {OptimizelyExperiment[]} List of Optimizely rollout experiments + * @param {ProjectConfig} configObj + * @param {FeatureVariablesMap} featureVariableIdMap + * @param {string} featureId + * @param {Experiment[]} experiments + * @param {{[id: string]: FeatureVariable}} variableIdMap + * @returns {OptimizelyExperiment[]} List of Optimizely rollout experiments */ static getDeliveryRules( configObj: ProjectConfig, featureVariableIdMap: FeatureVariablesMap, featureId: string, - experiments: Experiment[] + experiments: Experiment[], + variableIdMap: {[id: string]: FeatureVariable} ): OptimizelyExperiment[] { - const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj); return experiments.map((experiment) => { return { id: experiment.id, @@ -339,13 +346,14 @@ export class OptimizelyConfig { * Get experiments mapped by their id's which are not part of a rollout * @param {ProjectConfig} configObj * @param {FeatureVariablesMap} featureIdVariableMap + * @param {{[id: string]: FeatureVariable}} variableIdMap * @returns {[id: string]: OptimizelyExperiment} Experiments mapped by id */ static getExperimentsMapById( configObj: ProjectConfig, - featureIdVariableMap: FeatureVariablesMap + featureIdVariableMap: FeatureVariablesMap, + variableIdMap: {[id: string]: FeatureVariable} ): { [id: string]: OptimizelyExperiment } { - const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj); const rolloutExperimentIds = this.getRolloutExperimentIds(configObj.rollouts); const experiments = configObj.experiments; @@ -391,15 +399,17 @@ export class OptimizelyConfig { /** * Gets Map of all FeatureFlags and associated experiment map inside it - * @param {ProjectConfig} configObj - * @param {FeatureVariablesMap} featureVariableIdMap - * @param {OptimizelyExperimentsMap} experimentsMapById - * @returns {OptimizelyFeaturesMap} OptimizelyFeature mapped by key + * @param {ProjectConfig} configObj + * @param {FeatureVariablesMap} featureVariableIdMap + * @param {OptimizelyExperimentsMap} experimentsMapById + * @param {{[id: string]: FeatureVariable}} variableIdMap + * @returns {OptimizelyFeaturesMap} OptimizelyFeature mapped by key */ static getFeaturesMap( configObj: ProjectConfig, featureVariableIdMap: FeatureVariablesMap, - experimentsMapById: OptimizelyExperimentsMap + experimentsMapById: OptimizelyExperimentsMap, + variableIdMap: {[id: string]: FeatureVariable} ): OptimizelyFeaturesMap { const featuresMap: OptimizelyFeaturesMap = {}; configObj.featureFlags.forEach((featureFlag) => { @@ -428,7 +438,8 @@ export class OptimizelyConfig { configObj, featureVariableIdMap, featureFlag.id, - rollout.experiments + rollout.experiments, + variableIdMap, ); } featuresMap[featureFlag.key] = { From 280f2a0917a2fa39f228ad2e1b50878c677dfaff Mon Sep 17 00:00:00 2001 From: raju-opti <129887101+raju-opti@users.noreply.github.com> Date: Thu, 8 Jun 2023 23:10:47 +0600 Subject: [PATCH 020/200] [FSSDK-9361] fix failing fsc test for sending all events on config update (#830) --- packages/optimizely-sdk/lib/optimizely/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index f7cdb696f..12b6d88ce 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -149,6 +149,8 @@ export default class Optimizely implements Client { NotificationRegistry.getNotificationCenter(config.sdkKey)?.sendNotifications( NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE ); + + this.updateOdpSettings(); }); const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); From 2f75caf89b1f2d8069802736cf1ddf5e5a859c5e Mon Sep 17 00:00:00 2001 From: raju-opti <129887101+raju-opti@users.noreply.github.com> Date: Mon, 12 Jun 2023 22:06:00 +0600 Subject: [PATCH 021/200] update CHANGELOG for release 4.9.4 (#832) --- packages/optimizely-sdk/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/optimizely-sdk/CHANGELOG.md b/packages/optimizely-sdk/CHANGELOG.md index 7e1b0a18c..7a8ca0782 100644 --- a/packages/optimizely-sdk/CHANGELOG.md +++ b/packages/optimizely-sdk/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [4.9.4] - June 8, 2023 + +### Performance Improvements +- Improve OptimizelyConfig class instantiation performance from O(n^2) to O(n) where n = number of feature flags ([#829](https://github.com/optimizely/javascript-sdk/pull/829)) + ## 5.0.0-beta May 4, 2023 From 6b88aafbbab6f8622ae7f4e830f63b709f3b5f50 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Sat, 8 Jul 2023 12:53:39 +0600 Subject: [PATCH 022/200] [FSSDK-9416] Refactor dependent promises typing in client onReady (#834) --- .../lib/modules/event_processor/eventQueue.ts | 7 +++++-- .../optimizely-sdk/lib/modules/event_processor/managed.ts | 2 +- .../event_processor/v1/v1EventProcessor.react_native.ts | 2 +- .../lib/modules/event_processor/v1/v1EventProcessor.ts | 2 +- packages/optimizely-sdk/lib/optimizely/index.ts | 7 +++++-- .../plugins/event_processor/forwarding_event_processor.ts | 4 +++- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts b/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts index a61d37918..6b71d5209 100644 --- a/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts +++ b/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts @@ -67,8 +67,9 @@ export class SingleEventQueue implements EventQueue { this.sink = sink } - start(): void { + start(): Promise { // no-op + return Promise.resolve() } stop(): Promise { @@ -114,9 +115,11 @@ export class DefaultEventQueue implements EventQueue { this.started = false } - start(): void { + start(): Promise { this.started = true // dont start the timer until the first event is enqueued + + return Promise.resolve(); } stop(): Promise { diff --git a/packages/optimizely-sdk/lib/modules/event_processor/managed.ts b/packages/optimizely-sdk/lib/modules/event_processor/managed.ts index 6d89843de..40b786380 100644 --- a/packages/optimizely-sdk/lib/modules/event_processor/managed.ts +++ b/packages/optimizely-sdk/lib/modules/event_processor/managed.ts @@ -14,7 +14,7 @@ * limitations under the License. */ export interface Managed { - start(): void + start(): Promise stop(): Promise } diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts index cc690dbee..ebc444580 100644 --- a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts +++ b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts @@ -205,7 +205,7 @@ export class LogTierV1EventProcessor implements EventProcessor { } public async start(): Promise { - this.queue.start() + await this.queue.start() this.unsubscribeNetInfo = addConnectionListener(this.connectionListener.bind(this)) await this.processPendingEvents() diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts index 108bf2e1c..6f4d6cabf 100644 --- a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts +++ b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts @@ -97,6 +97,6 @@ export class LogTierV1EventProcessor implements EventProcessor { } async start(): Promise { - this.queue.start() + await this.queue.start() } } diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 12b6d88ce..6ddfc54a3 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -179,12 +179,15 @@ export default class Optimizely implements Client { const eventProcessorStartedPromise = this.eventProcessor.start(); - const dependentPromises: Array | void> = [ + const dependentPromises: Array> = [ projectConfigManagerReadyPromise, eventProcessorStartedPromise, - config.odpManager?.initPromise, ]; + if (config.odpManager?.initPromise) { + dependentPromises.push(config.odpManager.initPromise); + } + this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { // If no odpManager exists yet, creates a new one if (config.odpManager != null) { diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts b/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts index dd0473ac1..94b71a2ae 100644 --- a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts +++ b/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts @@ -44,7 +44,9 @@ class ForwardingEventProcessor implements EventProcessor { } } - start(): void {} + start(): Promise { + return Promise.resolve(); + } stop(): Promise { return Promise.resolve(); From f75f1f9f9dc0d2f3d02cf5c36b87da3c20916e4b Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 20 Jul 2023 17:42:21 +0600 Subject: [PATCH 023/200] [FSSDK-9490] Prepare for release 5.0.0-beta2 (#835) --- packages/optimizely-sdk/CHANGELOG.md | 9 +++++++++ packages/optimizely-sdk/lib/index.browser.tests.js | 2 +- packages/optimizely-sdk/lib/index.lite.tests.js | 2 +- packages/optimizely-sdk/lib/index.node.tests.js | 2 +- packages/optimizely-sdk/lib/utils/enums/index.ts | 4 ++-- packages/optimizely-sdk/package-lock.json | 2 +- packages/optimizely-sdk/package.json | 2 +- packages/optimizely-sdk/tests/index.react_native.spec.ts | 2 +- 8 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/optimizely-sdk/CHANGELOG.md b/packages/optimizely-sdk/CHANGELOG.md index 7a8ca0782..42706859a 100644 --- a/packages/optimizely-sdk/CHANGELOG.md +++ b/packages/optimizely-sdk/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.0.0-beta2] - July 19, 2023 + +### Performance Improvements +- Improved OptimizelyConfig class instantiation performance from O(n^2) to O(n) where n = number of feature flags ([#828](https://github.com/optimizely/javascript-sdk/pull/828)) + +### Bug fixes + +- Fixed ODP config update issue on datafile update ([#830](https://github.com/optimizely/javascript-sdk/pull/830)) + ## [4.9.4] - June 8, 2023 ### Performance Improvements diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index 72d4d8635..6fb78cac2 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -188,7 +188,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta2'); }); it('should set the JavaScript client engine and version', function() { diff --git a/packages/optimizely-sdk/lib/index.lite.tests.js b/packages/optimizely-sdk/lib/index.lite.tests.js index 40cd7eb90..fb0b1fc1e 100644 --- a/packages/optimizely-sdk/lib/index.lite.tests.js +++ b/packages/optimizely-sdk/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta2'); }); }); }); diff --git a/packages/optimizely-sdk/lib/index.node.tests.js b/packages/optimizely-sdk/lib/index.node.tests.js index d7904d2cd..7327032a4 100644 --- a/packages/optimizely-sdk/lib/index.node.tests.js +++ b/packages/optimizely-sdk/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta2'); }); describe('event processor configuration', function() { diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts index af68dacc6..41c027740 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ b/packages/optimizely-sdk/lib/utils/enums/index.ts @@ -223,8 +223,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '5.0.0-beta'; -export const NODE_CLIENT_VERSION = '5.0.0-beta'; +export const BROWSER_CLIENT_VERSION = '5.0.0-beta2'; +export const NODE_CLIENT_VERSION = '5.0.0-beta2'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json index 4f08c5eb0..99ffb3674 100644 --- a/packages/optimizely-sdk/package-lock.json +++ b/packages/optimizely-sdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta", + "version": "5.0.0-beta2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index 1d3de606f..5ef40a165 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta", + "version": "5.0.0-beta2", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/packages/optimizely-sdk/tests/index.react_native.spec.ts b/packages/optimizely-sdk/tests/index.react_native.spec.ts index 3b7662c05..afa83d52d 100644 --- a/packages/optimizely-sdk/tests/index.react_native.spec.ts +++ b/packages/optimizely-sdk/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.0.0-beta'); + expect(optlyInstance.clientVersion).toEqual('5.0.0-beta2'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From b83a884a6be5279400a0b22ebaec83afca5f5680 Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:37:06 -0400 Subject: [PATCH 024/200] [FSSDK-8503]: Update Github Issue Templates. (#840) * Update Github Issue Templates --- .github/ISSUE_TEMPLATE/BUG-REPORT.yml | 106 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/ENHANCEMENT.yml | 45 +++++++++ .github/ISSUE_TEMPLATE/FEATURE-REQUEST.md | 4 + .github/ISSUE_TEMPLATE/config.yml | 5 + .github/issue_template.md | 39 -------- 5 files changed, 160 insertions(+), 39 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/BUG-REPORT.yml create mode 100644 .github/ISSUE_TEMPLATE/ENHANCEMENT.yml create mode 100644 .github/ISSUE_TEMPLATE/FEATURE-REQUEST.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/issue_template.md diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 000000000..855cdf50d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,106 @@ +name: 🐞 Bug +description: File a bug/issue +title: "[BUG] " +labels: ["bug", "needs-triage"] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: SDK Version + description: Version of the SDK in use? + validations: + required: true +- type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: true +- type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 1. With this config... + 1. Run '...' + 1. See error... + validations: + required: true +- type: dropdown + attributes: + label: SDK Type + description: Please select the type of JS SDK. + multiple: false + options: + - Browser + - Node + - React Native + - Edge/Lite + validations: + required: true +- type: textarea + attributes: + label: Node Version + description: What version of Node are you using? + validations: + required: false +- type: textarea + attributes: + label: Browsers impacted + description: What browsers are impacted? + validations: + required: false +- type: textarea + attributes: + label: Link + description: Link to code demonstrating the problem. + validations: + required: false +- type: textarea + attributes: + label: Logs + description: Logs/stack traces related to the problem (⚠️do not include sensitive information). + validations: + required: false +- type: dropdown + attributes: + label: Severity + description: What is the severity of the problem? + multiple: true + options: + - Blocking development + - Affecting users + - Minor issue + validations: + required: false +- type: textarea + attributes: + label: Workaround/Solution + description: Do you have any workaround or solution in mind for the problem? + validations: + required: false +- type: textarea + attributes: + label: "Recent Change" + description: Has this issue started happening after an update or experiment change? + validations: + required: false +- type: textarea + attributes: + label: Conflicts + description: Are there other libraries/dependencies potentially in conflict? + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml new file mode 100644 index 000000000..79c53247b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml @@ -0,0 +1,45 @@ +name: ✨Enhancement +description: Create a new ticket for a Enhancement/Tech-initiative for the benefit of the SDK which would be considered for a minor version update. +title: "[ENHANCEMENT] <title>" +labels: ["enhancement"] +body: + - type: textarea + id: description + attributes: + label: "Description" + description: Briefly describe the enhancement in a few sentences. + placeholder: Short description... + validations: + required: true + - type: textarea + id: benefits + attributes: + label: "Benefits" + description: How would the enhancement benefit to your product or usage? + placeholder: Benefits... + validations: + required: true + - type: textarea + id: detail + attributes: + label: "Detail" + description: How would you like the enhancement to work? Please provide as much detail as possible + placeholder: Detailed description... + validations: + required: false + - type: textarea + id: examples + attributes: + label: "Examples" + description: Are there any examples of this enhancement in other products/services? If so, please provide links or references. + placeholder: Links/References... + validations: + required: false + - type: textarea + id: risks + attributes: + label: "Risks/Downsides" + description: Do you think this enhancement could have any potential downsides or risks? + placeholder: Risks/Downsides... + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md new file mode 100644 index 000000000..a061f3356 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md @@ -0,0 +1,4 @@ +<!-- + Thanks for filing in issue! Are you requesting a new feature? If so, please share your feedback with us on the following link. +--> +## Feedback requesting a new feature can be shared [here.](https://feedback.optimizely.com/) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..d28ef3dd4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 💡Feature Requests + url: https://feedback.optimizely.com/ + about: Feedback requesting a new feature can be shared here. \ No newline at end of file diff --git a/.github/issue_template.md b/.github/issue_template.md deleted file mode 100644 index 01f3e9b9e..000000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,39 +0,0 @@ -<!-- - Thanks for filing in issue! Are you proposing an enhancement or reporting a bug? - - If proposing an enhancement, please describe your use case in as much detail as you think is needed to convey the value of the enhancement. ---> -## How would the enhancement work? - -## When would the enhancement be useful? - -<!-- - If reporting a bug, please include the following info: ---> - -## What I wanted to do - -## What I expected to happen - -## What actually happened - -## Steps to reproduce -Link to repository that can reproduce the issue: <link> - -<!-- - OR provide the following. - If possible, whittle down your issue into a [short, self-contained, correct example](http://sscce.org/). ---> - -**`@optimizely/optimizely-sdk` version:** - -<!-- ...and whichever of the following are applicable: --> - -**Browser and version:** - -**`node` version:** - -**`npm` version:** - -Versions of any other relevant tools (like module bundlers, transpilers, etc.): - From 2f892bee62a6a24afc1e833506bd5dde744016df Mon Sep 17 00:00:00 2001 From: Rafin Akther Utshaw <126907888+rafinutshaw-optimizely@users.noreply.github.com> Date: Thu, 3 Aug 2023 00:16:33 +0600 Subject: [PATCH 025/200] [FSSDK-8740] Add odp functionalities for rn (#839) * Add odp for rn * Update copyright --- packages/optimizely-sdk/lib/index.react_native.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/optimizely-sdk/lib/index.react_native.ts b/packages/optimizely-sdk/lib/index.react_native.ts index 31dd92d45..4f5019409 100644 --- a/packages/optimizely-sdk/lib/index.react_native.ts +++ b/packages/optimizely-sdk/lib/index.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2022 Optimizely + * Copyright 2019-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import { createEventProcessor } from './plugins/event_processor/index.react_nati import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { createHttpPollingDatafileManager } from './plugins/datafile_manager/react_native_http_polling_datafile_manager'; +import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; const logger = getLogger(); setLogHandler(loggerPlugin.createLogger()); @@ -111,6 +112,7 @@ const createInstance = function(config: Config): Client | null { datafileManager: config.sdkKey ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, notificationCenter, isValidInstance: isValidInstance, + odpManager: new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), }; // If client engine is react, convert it to react native. From bc51f5d5367d88d4cd05f83d98d0081c3534dd39 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:31:25 -0400 Subject: [PATCH 026/200] [FSSDK-9506] Allow any polling interval but implement warning log (#841) * Add dev container * Update package lock * Add Jest extension settings * Update config defaults; Added jsdoc * Update logged warning message * Allow polling under 1 second; only warn < 30s * Fix jestCommandLine * WIP: Add polling testing spec The other file is WAY too long. Need to fix test fails. * Add typeRoots * Allow any intervals but only soft warn about them * Finish unit tests around soft warning * Update copyright * Add postStartCommand * NIT: Apache link * Update node versions in CI * Remove dev container postStartCommand * Rename update interval check func * Add back Node 14 to CI * Remove Node v14 from CI * Remove unnecessary function * Fix backward logic * Allow for 30s, but not 29s for warn * Add back back Node v14 * Run only unit test for node 14 * Add console.log * Remove typeRoots * Reuse existing TestDatafileManager * Revert package-lock upgrade * Return typeRoots * Remove console.log & return CI * Re-remove typeRoots --- .devcontainer/devcontainer.json | 20 ++++++ .github/workflows/javascript.yml | 4 +- .vscode/settings.json | 5 ++ .../lib/modules/datafile-manager/config.ts | 14 +++-- .../datafile-manager/datafileManager.ts | 5 +- .../httpPollingDatafileManager.ts | 26 ++++---- .../tests/httpPollingDatafileManager.spec.ts | 2 +- .../httpPollingDatafileManagerPolling.spec.ts | 61 +++++++++++++++++++ 8 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .vscode/settings.json create mode 100644 packages/optimizely-sdk/tests/httpPollingDatafileManagerPolling.spec.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..acd068bff --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Javascript SDK", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-18-bookworm", + + "postCreateCommand": "cd /workspaces/javascript-sdk/packages/optimizely-sdk && npm install -g npm && npm install", + + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "Gruntfuggly.todo-tree", + "github.vscode-github-actions", + "Orta.vscode-jest", + "ms-vscode.test-adapter-converter" + ] + } + } +} diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 18c063677..426192b7d 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 12 + node-version: 14 cache-dependency-path: packages/optimizely-sdk/package-lock.json cache: 'npm' - name: Run linting @@ -118,7 +118,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 12 + node-version: 14 cache: 'npm' cache-dependency-path: ${{ matrix.package }}/package-lock.json - name: Test sub packages diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..67bbd4ff1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "jest.rootPath": "/workspaces/javascript-sdk/packages/optimizely-sdk", + "jest.jestCommandLine": "./node_modules/.bin/jest", + "jest.autoRevealOutput": "on-exec-error" +} \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/config.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/config.ts index 18de89eff..55e69a33e 100644 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/config.ts +++ b/packages/optimizely-sdk/lib/modules/datafile-manager/config.ts @@ -1,11 +1,11 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,15 @@ * limitations under the License. */ -export const DEFAULT_UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes +const DEFAULT_UPDATE_INTERVAL_MINUTES = 5; +/** Standard interval (5 minutes in milliseconds) for polling datafile updates.; */ +export const DEFAULT_UPDATE_INTERVAL = DEFAULT_UPDATE_INTERVAL_MINUTES * 60 * 1000; -export const MIN_UPDATE_INTERVAL = 1000; +const MIN_UPDATE_INTERVAL_SECONDS = 30; +/** Minimum allowed interval (30 seconds in milliseconds) for polling datafile updates. */ +export const MIN_UPDATE_INTERVAL = MIN_UPDATE_INTERVAL_SECONDS * 1000; + +export const UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE = `Polling intervals below ${MIN_UPDATE_INTERVAL_SECONDS} seconds are not recommended.`; export const DEFAULT_URL_TEMPLATE = `https://cdn.optimizely.com/datafiles/%s.json`; diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts index 5730786c0..9e748d174 100644 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts +++ b/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts @@ -1,11 +1,11 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -40,6 +40,7 @@ export interface DatafileManagerConfig { autoUpdate?: boolean; datafile?: string; sdkKey: string; + /** Polling interval in milliseconds to check for datafile updates. */ updateInterval?: number; urlTemplate?: string; cache?: PersistentKeyValueCache; diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts index fcf2c0efd..ee595a526 100644 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ b/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,7 +19,7 @@ import { sprintf } from '../../../lib/utils/fns'; import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; import EventEmitter, { Disposer } from './eventEmitter'; import { AbortableRequest, Response, Headers } from './http'; -import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE } from './config'; +import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './config'; import BackoffController from './backoffController'; import PersistentKeyValueCache from './persistentKeyValueCache'; @@ -30,10 +30,6 @@ const logger = getLogger('DatafileManager'); const UPDATE_EVT = 'update'; -function isValidUpdateInterval(updateInterval: number): boolean { - return updateInterval >= MIN_UPDATE_INTERVAL; -} - function isSuccessStatusCode(statusCode: number): boolean { return statusCode >= 200 && statusCode < 400; } @@ -124,8 +120,8 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana this.cacheKey = 'opt-datafile-' + sdkKey; this.sdkKey = sdkKey; this.isReadyPromiseSettled = false; - this.readyPromiseResolver = (): void => {}; - this.readyPromiseRejecter = (): void => {}; + this.readyPromiseResolver = (): void => { }; + this.readyPromiseRejecter = (): void => { }; this.readyPromise = new Promise((resolve, reject) => { this.readyPromiseResolver = resolve; this.readyPromiseRejecter = reject; @@ -145,16 +141,20 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana this.datafileUrl = sprintf(urlTemplate, sdkKey); this.emitter = new EventEmitter(); + this.autoUpdate = autoUpdate; - if (isValidUpdateInterval(updateInterval)) { - this.updateInterval = updateInterval; - } else { - logger.warn('Invalid updateInterval %s, defaulting to %s', updateInterval, DEFAULT_UPDATE_INTERVAL); - this.updateInterval = DEFAULT_UPDATE_INTERVAL; + + this.updateInterval = updateInterval; + if (this.updateInterval < MIN_UPDATE_INTERVAL) { + logger.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE); } + this.currentTimeout = null; + this.currentRequest = null; + this.backoffController = new BackoffController(); + this.syncOnCurrentRequestComplete = false; } diff --git a/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts b/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts index 288a30367..ffb8e565b 100644 --- a/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts +++ b/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts @@ -35,7 +35,7 @@ import BackoffController from '../lib/modules/datafile-manager/backoffController // Test implementation: // - Does not make any real requests: just resolves with queued responses (tests push onto queuedResponses) -class TestDatafileManager extends HttpPollingDatafileManager { +export class TestDatafileManager extends HttpPollingDatafileManager { queuedResponses: (Response | Error)[] = []; responsePromises: Promise<Response>[] = []; diff --git a/packages/optimizely-sdk/tests/httpPollingDatafileManagerPolling.spec.ts b/packages/optimizely-sdk/tests/httpPollingDatafileManagerPolling.spec.ts new file mode 100644 index 000000000..a5be36eed --- /dev/null +++ b/packages/optimizely-sdk/tests/httpPollingDatafileManagerPolling.spec.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2023 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resetCalls, spy, verify } from 'ts-mockito'; +import { LogLevel, LoggerFacade, getLogger, setLogLevel } from '../lib/modules/logging'; +import { UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from '../lib/modules/datafile-manager/config'; +import { TestDatafileManager } from './httpPollingDatafileManager.spec'; + +describe('HttpPollingDatafileManager polling', () => { + let spiedLogger: LoggerFacade; + + const loggerName = 'DatafileManager'; + const sdkKey = 'not-real-sdk'; + + beforeAll(() => { + setLogLevel(LogLevel.DEBUG); + const actualLogger = getLogger(loggerName); + spiedLogger = spy(actualLogger); + }); + + beforeEach(() => { + resetCalls(spiedLogger); + }); + + + it('should log polling interval below 30 seconds', () => { + const below30Seconds = 29 * 1000; + + new TestDatafileManager({ + sdkKey, + updateInterval: below30Seconds, + }); + + + verify(spiedLogger.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE)).once(); + }); + + it('should not log when polling interval above 30s', () => { + const oneMinute = 60 * 1000; + + new TestDatafileManager({ + sdkKey, + updateInterval: oneMinute, + }); + + verify(spiedLogger.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE)).never(); + }); +}); From 4cea2f1e64396ccb795380d4ea8ebcf8270d8fc4 Mon Sep 17 00:00:00 2001 From: Rafin Akther Utshaw <126907888+rafinutshaw-optimizely@users.noreply.github.com> Date: Fri, 4 Aug 2023 00:18:05 +0600 Subject: [PATCH 027/200] [FSSDK-8740] Add script for windows (#843) * Add script for windows * Remove postbuild * Add postbuild * Update postbuild to postbuild:win --- packages/optimizely-sdk/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index 5ef40a165..b1a8bf351 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -9,6 +9,7 @@ "typings": "dist/index.browser.d.ts", "scripts": { "clean": "rm -rf dist", + "clean:win": "(if exist dist rd /s/q dist)", "lint": "tsc --noEmit && eslint 'lib/**/*.js' 'lib/**/*.ts'", "test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js' && jest", "posttest": "npm run lint", @@ -18,10 +19,12 @@ "test-karma-local": "karma start karma.local_chrome.bs.conf.js && npm run build-browser-umd && karma start karma.local_chrome.umd.conf.js", "prebuild": "npm run clean", "build": "rollup -c && cp dist/index.lite.d.ts dist/optimizely.lite.es.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.es.min.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.min.d.ts", + "build:win": "rollup -c && type nul > dist/optimizely.lite.es.d.ts && type nul > dist/optimizely.lite.es.min.d.ts && type nul > dist/optimizely.lite.min.d.ts", "build-browser-umd": "rollup -c --config-umd", "coveralls": "nyc --reporter=lcov npm test", "prepare": "npm run build", - "prepublishOnly": "npm test && npm run test-ci" + "prepublishOnly": "npm test && npm run test-ci", + "postbuild:win": "@powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.min.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.min.d.ts\"" }, "repository": { "type": "git", From c4b83738b80325797fa65e4986947a08b2921aac Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Sat, 12 Aug 2023 00:44:04 +0600 Subject: [PATCH 028/200] [FSSDK-9562] refactor odp event api manager to handle odp config (#844) --- .../lib/core/odp/odp_event_api_manager.ts | 30 ++-- .../lib/core/odp/odp_event_manager.ts | 7 +- .../optimizely-sdk/lib/index.browser.tests.js | 2 +- .../odp/event_api_manager/index.browser.ts | 10 +- .../odp/event_api_manager/index.node.ts | 14 +- packages/optimizely-sdk/tests/jsconfig.json | 7 + .../tests/odpEventApiManager.spec.ts | 53 ++++++- .../tests/odpEventManager.spec.ts | 147 +++++++++++------- 8 files changed, 193 insertions(+), 77 deletions(-) create mode 100644 packages/optimizely-sdk/tests/jsconfig.json diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts index 09fcfd230..30d5eb7a1 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts @@ -17,14 +17,17 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { OdpEvent } from './odp_event'; import { RequestHandler } from '../../utils/http_request_handler/http'; +import { OdpConfig } from './odp_config'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; +export const ODP_CONFIG_NOT_READY_MESSAGE = 'ODP config not ready'; /** * Manager for communicating with the Optimizely Data Platform REST API */ export interface IOdpEventApiManager { - sendEvents(apiKey: string, apiHost: string, events: OdpEvent[]): Promise<boolean>; + sendEvents(events: OdpEvent[]): Promise<boolean>; + updateSettings(odpConfig: OdpConfig): void; } /** @@ -43,6 +46,11 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { */ private readonly requestHandler: RequestHandler; + /** + * ODP configuration settings for identifying the target API and segments + */ + protected odpConfig?: OdpConfig; + /** * Creates instance to access Optimizely Data Platform (ODP) REST API * @param requestHandler Desired request handler for testing @@ -53,22 +61,28 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { this.logger = logger; } + /** + * Updates odpConfig of the api manager instance + * @param odpConfig + */ + updateSettings(odpConfig: OdpConfig): void { + this.odpConfig = odpConfig; + } + getLogger(): LogHandler { return this.logger; } /** * Service for sending ODP events to REST API - * @param apiKey ODP public key - * @param apiHost Host of ODP endpoint * @param events ODP events to send * @returns Retry is true - if network or server error (5xx), otherwise false */ - async sendEvents(apiKey: string, apiHost: string, events: OdpEvent[]): Promise<boolean> { + async sendEvents(events: OdpEvent[]): Promise<boolean> { let shouldRetry = false; - if (!apiKey || !apiHost) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (Parameters apiKey or apiHost invalid)`); + if (!this.odpConfig?.isReady()) { + this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${ODP_CONFIG_NOT_READY_MESSAGE})`); return shouldRetry; } @@ -81,7 +95,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { return shouldRetry; } - const { method, endpoint, headers, data } = this.generateRequestData(apiHost, apiKey, events); + const { method, endpoint, headers, data } = this.generateRequestData(events); let statusCode = 0; try { @@ -111,8 +125,6 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { protected abstract shouldSendEvents(events: OdpEvent[]): boolean; protected abstract generateRequestData( - apiHost: string, - apiKey: string, events: OdpEvent[] ): { method: string; diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts index ad444780f..e8a6744e2 100644 --- a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts +++ b/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts @@ -149,6 +149,8 @@ export abstract class OdpEventManager implements IOdpEventManager { this.clientVersion = clientVersion; this.initParams(batchSize, queueSize, flushInterval); this.state = STATE.STOPPED; + + this.apiManager.updateSettings(odpConfig); } protected abstract initParams( @@ -163,6 +165,7 @@ export abstract class OdpEventManager implements IOdpEventManager { */ updateSettings(newConfig: OdpConfig): void { this.odpConfig = newConfig; + this.apiManager.updateSettings(newConfig); } /** @@ -340,7 +343,7 @@ export abstract class OdpEventManager implements IOdpEventManager { */ private makeAndSend1Batch(): void { const batch = new Array<OdpEvent>(); - + // remove a batch from the queue for (let count = 0; count < this.batchSize; count += 1) { const event = this.queue.shift(); @@ -357,7 +360,7 @@ export abstract class OdpEventManager implements IOdpEventManager { let shouldRetry: boolean; let attemptNumber = 0; do { - shouldRetry = await this.apiManager.sendEvents(this.odpConfig.apiKey, this.odpConfig.apiHost, batch); + shouldRetry = await this.apiManager.sendEvents(batch); attemptNumber += 1; } while (shouldRetry && attemptNumber < MAX_RETRIES); }); diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index 6fb78cac2..b813c9c34 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -1111,7 +1111,7 @@ describe('javascript-sdk (Browser)', function() { clock.tick(100); - const [, , events] = apiManager.sendEvents.getCall(0).args; + const [events] = apiManager.sendEvents.getCall(0).args; const [firstEvent] = events; assert.equal(firstEvent.action, 'client_initialized'); assert.equal(firstEvent.type, 'fullstack'); diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts index f01e82b24..5a13b8c06 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts @@ -2,6 +2,7 @@ import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; import { ODP_EVENT_BROWSER_ENDPOINT } from '../../../utils/enums'; +import { ODP_CONFIG_NOT_READY_MESSAGE } from '../../../core/odp/odp_event_api_manager'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; @@ -15,10 +16,15 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { } protected generateRequestData( - apiHost: string, - apiKey: string, events: OdpEvent[] ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { + // the caller should ensure odpConfig is ready before calling + if (!this.odpConfig?.isReady()) { + this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); + throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); + } + + const apiKey = this.odpConfig.apiKey; const method = 'GET'; const event = events[0]; const url = new URL(ODP_EVENT_BROWSER_ENDPOINT); diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts index f61f33d10..1d04bc9d3 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts +++ b/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts @@ -1,16 +1,24 @@ import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; - +import { LogLevel } from '../../../modules/logging'; +import { ODP_CONFIG_NOT_READY_MESSAGE } from '../../../core/odp/odp_event_api_manager'; export class NodeOdpEventApiManager extends OdpEventApiManager { protected shouldSendEvents(events: OdpEvent[]): boolean { return true; } protected generateRequestData( - apiHost: string, - apiKey: string, events: OdpEvent[] ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { + // the caller should ensure odpConfig is ready before calling + if (!this.odpConfig?.isReady()) { + this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); + throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); + } + + const apiHost = this.odpConfig.apiHost; + const apiKey = this.odpConfig.apiKey; + return { method: 'POST', endpoint: `${apiHost}/v3/events`, diff --git a/packages/optimizely-sdk/tests/jsconfig.json b/packages/optimizely-sdk/tests/jsconfig.json new file mode 100644 index 000000000..594d9e97d --- /dev/null +++ b/packages/optimizely-sdk/tests/jsconfig.json @@ -0,0 +1,7 @@ +{ + "typeAcquisition": { + "include": [ + "jest" + ] + } +} diff --git a/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts b/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts index b9dabfd4b..0ed4d4dc7 100644 --- a/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts @@ -16,14 +16,13 @@ /// <reference types="jest" /> -import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; +import { anyString, anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; import { NodeOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; import { OdpEvent } from '../lib/core/odp/odp_event'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; +import { OdpConfig } from '../lib/core/odp/odp_config'; -const VALID_ODP_PUBLIC_KEY = 'not-real-api-key'; -const ODP_REST_API_HOST = 'https://events.example.com/v2/api'; const data1 = new Map<string, unknown>(); data1.set('key11', 'value-1'); data1.set('key12', true); @@ -36,6 +35,11 @@ const ODP_EVENTS = [ new OdpEvent('t2', 'a2', new Map([['id-key-2', 'id-value-2']]), data2), ]; +const API_KEY = 'test-api-key'; +const API_HOST = 'https://odp.example.com/'; + +const odpConfig = new OdpConfig(API_KEY, API_HOST, []); + describe('NodeOdpEventApiManager', () => { let mockLogger: LogHandler; let mockRequestHandler: RequestHandler; @@ -50,7 +54,12 @@ describe('NodeOdpEventApiManager', () => { resetCalls(mockRequestHandler); }); - const managerInstance = () => new NodeOdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); + const managerInstance = () => { + const manager = new NodeOdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); + manager.updateSettings(odpConfig); + return manager; + } + const abortableRequest = (statusCode: number, body: string) => { return { abort: () => {}, @@ -68,7 +77,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); + const shouldRetry = await manager.sendEvents(ODP_EVENTS); expect(shouldRetry).toBe(false); verify(mockLogger.log(anything(), anyString())).never(); @@ -80,7 +89,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); + const shouldRetry = await manager.sendEvents(ODP_EVENTS); expect(shouldRetry).toBe(false); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (400)')).once(); @@ -92,7 +101,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); + const shouldRetry = await manager.sendEvents(ODP_EVENTS); expect(shouldRetry).toBe(true); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (500)')).once(); @@ -105,9 +114,37 @@ describe('NodeOdpEventApiManager', () => { }); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); + const shouldRetry = await manager.sendEvents(ODP_EVENTS); expect(shouldRetry).toBe(true); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (Request timed out)')).once(); }); + + it('should send events to updated host on settings update', async () => { + when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ + abort: () => {}, + responsePromise: Promise.reject(new Error('Request timed out')), + }); + + const manager = managerInstance(); + + await manager.sendEvents(ODP_EVENTS); + + const updatedOdpConfig = new OdpConfig( + 'updated-key', + 'https://updatedhost.test/', + ['updated-seg'], + ) + + manager.updateSettings(updatedOdpConfig); + await manager.sendEvents(ODP_EVENTS); + + verify(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).twice(); + + const [initUrl] = capture(mockRequestHandler.makeRequest).first(); + expect(initUrl).toEqual(`${API_HOST}/v3/events`); + + const [finalUrl] = capture(mockRequestHandler.makeRequest).last(); + expect(finalUrl).toEqual(`${updatedOdpConfig.apiHost}/v3/events`); + }); }); diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts index de014698a..466b0368b 100644 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ b/packages/optimizely-sdk/tests/odpEventManager.spec.ts @@ -20,10 +20,9 @@ import { STATE } from '../lib/core/odp/odp_event_manager'; import { BrowserOdpEventManager } from "../lib/plugins/odp/event_manager/index.browser"; import { NodeOdpEventManager, NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; -import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; +import { IOdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; import { LogHandler, LogLevel } from '../lib/modules/logging'; import { OdpEvent } from '../lib/core/odp/odp_event'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; @@ -135,16 +134,15 @@ const abortableRequest = (statusCode: number, body: string) => { describe('OdpEventManager', () => { let mockLogger: LogHandler; - let mockApiManager: OdpEventApiManager; + let mockApiManager: IOdpEventApiManager; let odpConfig: OdpConfig; let logger: LogHandler; - let apiManager: OdpEventApiManager; + let apiManager: IOdpEventApiManager; beforeAll(() => { mockLogger = mock<LogHandler>(); - mockApiManager = mock<OdpEventApiManager>(); - + mockApiManager = mock<IOdpEventApiManager>(); odpConfig = new OdpConfig(API_KEY, API_HOST, []); logger = instance(mockLogger); apiManager = instance(mockApiManager); @@ -155,6 +153,55 @@ describe('OdpEventManager', () => { resetCalls(mockApiManager); }); + it('should update api manager setting with odp config on instantiation', () => { + when(mockApiManager.sendEvents(anything())).thenResolve(false); + when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + + const apiManager = instance(mockApiManager); + + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + }); + + const [passedConfig] = capture(mockApiManager.updateSettings).last(); + expect(passedConfig).toEqual(odpConfig); + }); + + it('should update api manager setting with updatetd odp config on updateSettings', () => { + when(mockApiManager.sendEvents(anything())).thenResolve(false); + when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + + const apiManager = instance(mockApiManager); + + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + }); + + const updatedOdpConfig = new OdpConfig( + 'updated-key', + 'https://updatedhost.test/', + ['updated-seg'], + ) + + eventManager.updateSettings(updatedOdpConfig); + + verify(mockApiManager.updateSettings(anything())).twice(); + + const [initConfig] = capture(mockApiManager.updateSettings).first(); + expect(initConfig).toEqual(odpConfig); + + const [finalConfig] = capture(mockApiManager.updateSettings).last(); + expect(finalConfig).toEqual(updatedOdpConfig); + }); + it('should log and discard events when event manager not running', () => { const eventManager = new OdpEventManager({ odpConfig, @@ -277,7 +324,7 @@ describe('OdpEventManager', () => { }); it('should dispatch events in correct number of batches', async () => { - when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(false); + when(mockApiManager.sendEvents(anything())).thenResolve(false); const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -297,7 +344,7 @@ describe('OdpEventManager', () => { // ...there should be 3 batches: // batch #1 with 10, batch #2 with 10, and batch #3 (after flushInterval lapsed) with 5 = 25 events - verify(mockApiManager.sendEvents(anything(), anything(), anything())).thrice(); + verify(mockApiManager.sendEvents(anything())).thrice(); }); it('should dispatch events with correct payload', async () => { @@ -316,10 +363,8 @@ describe('OdpEventManager', () => { await pause(1000); // sending 1 batch of 2 events after flushInterval since batchSize is 10 - verify(mockApiManager.sendEvents(anything(), anything(), anything())).once(); - const [apiKey, apiHost, events] = capture(mockApiManager.sendEvents).last(); - expect(apiKey).toEqual(API_KEY); - expect(apiHost).toEqual(API_HOST); + verify(mockApiManager.sendEvents(anything())).once(); + const [events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toEqual(2); expect(events[0].identifiers.size).toEqual(PROCESSED_EVENTS[0].identifiers.size); expect(events[0].data.size).toEqual(PROCESSED_EVENTS[0].data.size); @@ -329,7 +374,7 @@ describe('OdpEventManager', () => { it('should retry failed events', async () => { // all events should fail ie shouldRetry = true - when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(true); + when(mockApiManager.sendEvents(anything())).thenResolve(true); const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -349,11 +394,11 @@ describe('OdpEventManager', () => { await pause(1500); // retry 3x (default) for 2 batches or 6 calls to attempt to process - verify(mockApiManager.sendEvents(anything(), anything(), anything())).times(6); + verify(mockApiManager.sendEvents(anything())).times(6); }); it('should flush all scheduled events before stopping', async () => { - when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(false); + when(mockApiManager.sendEvents(anything())).thenResolve(false); const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -378,11 +423,11 @@ describe('OdpEventManager', () => { }); it('should prepare correct payload for register VUID', async () => { - const mockRequestHandler: RequestHandler = mock<RequestHandler>(); - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, '') - ); - const apiManager = new OdpEventApiManager(instance(mockRequestHandler), logger); + when(mockApiManager.sendEvents(anything())).thenResolve(false); + when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + + const apiManager = instance(mockApiManager); + const eventManager = new OdpEventManager({ odpConfig, apiManager, @@ -390,44 +435,45 @@ describe('OdpEventManager', () => { clientEngine, clientVersion, batchSize: 10, - flushInterval: 100, + flushInterval: 250, }); + const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; + const fsUserId = 'test-fs-user-id'; eventManager.start(); eventManager.registerVuid(vuid); await pause(1500); - const [requestUrl, headers, method, data] = capture(mockRequestHandler.makeRequest).last(); - expect(requestUrl).toEqual(`${API_HOST}/v3/events`); - expect(headers['Content-Type']).toEqual('application/json'); - expect(headers['x-api-key']).toEqual('test-api-key'); - expect(method).toEqual('POST'); - const events = JSON.parse(data as string); - const event = events[0]; + const [events] = capture(mockApiManager.sendEvents).last(); + expect(events.length).toBe(1); + + const [event] = events; expect(event.type).toEqual('fullstack'); expect(event.action).toEqual(ODP_EVENT_ACTION.INITIALIZED); - expect(event.identifiers).toEqual({ vuid: vuid }); - expect(event.data.idempotence_id.length).toBe(36); // uuid length - expect(event.data.data_source_type).toEqual('sdk'); - expect(event.data.data_source).toEqual('javascript-sdk'); - expect(event.data.data_source_version).not.toBeNull(); + expect(event.identifiers).toEqual(new Map([['vuid', vuid]])); + expect((event.data.get("idempotence_id") as string).length).toBe(36); // uuid length + expect((event.data.get("data_source_type") as string)).toEqual('sdk'); + expect((event.data.get("data_source") as string)).toEqual('javascript-sdk'); + expect(event.data.get("data_source_version") as string).not.toBeNull(); }); - it('should prepare correct payload for identify user', async () => { - const mockRequestHandler: RequestHandler = mock<RequestHandler>(); - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, '') - ); - const apiManager = new OdpEventApiManager(instance(mockRequestHandler), logger); + it('should send correct event payload for identify user', async () => { + when(mockApiManager.sendEvents(anything())).thenResolve(false); + when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + + const apiManager = instance(mockApiManager); + const eventManager = new OdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, - flushInterval: 100, + batchSize: 10, + flushInterval: 250, }); + const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; const fsUserId = 'test-fs-user-id'; @@ -435,20 +481,17 @@ describe('OdpEventManager', () => { eventManager.identifyUser(fsUserId, vuid); await pause(1500); - const [requestUrl, headers, method, data] = capture(mockRequestHandler.makeRequest).last(); - expect(requestUrl).toEqual(`${API_HOST}/v3/events`); - expect(headers['Content-Type']).toEqual('application/json'); - expect(headers['x-api-key']).toEqual('test-api-key'); - expect(method).toEqual('POST'); - const events = JSON.parse(data as string); - const event = events[0]; + const [events] = capture(mockApiManager.sendEvents).last(); + expect(events.length).toBe(1); + + const [event] = events; expect(event.type).toEqual(ODP_DEFAULT_EVENT_TYPE); expect(event.action).toEqual(ODP_EVENT_ACTION.IDENTIFIED); - expect(event.identifiers).toEqual({ vuid: vuid, fs_user_id: fsUserId }); - expect(event.data.idempotence_id.length).toBe(36); // uuid length - expect(event.data.data_source_type).toEqual('sdk'); - expect(event.data.data_source).toEqual('javascript-sdk'); - expect(event.data.data_source_version).not.toBeNull(); + expect(event.identifiers).toEqual(new Map([['vuid', vuid], ['fs_user_id', fsUserId]])); + expect((event.data.get("idempotence_id") as string).length).toBe(36); // uuid length + expect((event.data.get("data_source_type") as string)).toEqual('sdk'); + expect((event.data.get("data_source") as string)).toEqual('javascript-sdk'); + expect(event.data.get("data_source_version") as string).not.toBeNull(); }); it('should apply updated ODP configuration when available', () => { From 9384463e80d2f922b93f732a2841ae91b83c7414 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 14 Aug 2023 20:23:42 +0600 Subject: [PATCH 029/200] [FSSDK-8219] removed unnecessary subpackage dependecny (#845) --- packages/optimizely-sdk/package-lock.json | 35 +---------------------- packages/optimizely-sdk/package.json | 1 - 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json index 99ffb3674..eb0b7d74f 100644 --- a/packages/optimizely-sdk/package-lock.json +++ b/packages/optimizely-sdk/package-lock.json @@ -1353,39 +1353,6 @@ "fastq": "^1.6.0" } }, - "@optimizely/js-sdk-datafile-manager": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-datafile-manager/-/js-sdk-datafile-manager-0.9.5.tgz", - "integrity": "sha512-O4ujr1nBBAQBtx8YoKNpzzaEZgsE+aU4dxubT17ePqv/YVUWE+JOY21tSRrqZy/BlbbyzL+ElT8hrGB5ZzVoIQ==", - "requires": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0", - "decompress-response": "^4.2.1" - } - }, - "@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "requires": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } - } - }, "@react-native-async-storage/async-storage": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", @@ -6184,7 +6151,7 @@ }, "json-schema": { "version": "0.4.0", - "resolved": "https://nexus.es.ecg.tools/repository/npm-all/json-schema/-/json-schema-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-traverse": { diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index b1a8bf351..f1e13e25c 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -43,7 +43,6 @@ }, "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/optimizely-sdk", "dependencies": { - "@optimizely/js-sdk-datafile-manager": "^0.9.5", "decompress-response": "^4.2.1", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", From 38f276ba7595c016b87607a82c61bc0310a629a9 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 14 Aug 2023 21:48:37 +0600 Subject: [PATCH 030/200] [FSSDK-8219] removed datafile manager subpackage (#846) --- .github/workflows/javascript.yml | 2 +- README.md | 1 - packages/datafile-manager/.eslintrc.js | 46 - packages/datafile-manager/.gitignore | 1 - packages/datafile-manager/.nvmrc | 1 - packages/datafile-manager/.prettierrc | 10 - packages/datafile-manager/CHANGELOG.md | 108 - packages/datafile-manager/LICENSE | 202 - packages/datafile-manager/README.md | 49 - .../async-storage.ts | 37 - .../__test__/backoffController.spec.ts | 63 - .../__test__/browserDatafileManager.spec.ts | 105 - .../__test__/browserRequest.spec.ts | 132 - .../__test__/eventEmitter.spec.ts | 116 - .../httpPollingDatafileManager.spec.ts | 711 --- .../__test__/nodeDatafileManager.spec.ts | 186 - .../__test__/nodeRequest.spec.ts | 216 - .../reactNativeAsyncStorageCache.spec.ts | 52 - .../datafile-manager/__test__/testUtils.ts | 27 - packages/datafile-manager/jest.config.js | 211 - packages/datafile-manager/package-lock.json | 5355 ----------------- packages/datafile-manager/package.json | 69 - .../datafile-manager/src/backoffController.ts | 46 - .../src/browserDatafileManager.ts | 32 - .../datafile-manager/src/browserRequest.ts | 96 - packages/datafile-manager/src/config.ts | 27 - .../datafile-manager/src/datafileManager.ts | 50 - packages/datafile-manager/src/eventEmitter.ts | 62 - packages/datafile-manager/src/http.ts | 37 - .../src/httpPollingDatafileManager.ts | 339 -- .../datafile-manager/src/index.browser.ts | 18 - packages/datafile-manager/src/index.node.ts | 18 - .../src/index.react_native.ts | 18 - .../src/nodeDatafileManager.ts | 52 - packages/datafile-manager/src/nodeRequest.ts | 154 - .../src/persistentKeyValueCache.ts | 59 - .../src/reactNativeAsyncStorageCache.ts | 40 - .../src/reactNativeDatafileManager.ts | 34 - packages/datafile-manager/tsconfig.json | 12 - packages/optimizely-sdk/README.md | 3 +- 40 files changed, 2 insertions(+), 8795 deletions(-) delete mode 100644 packages/datafile-manager/.eslintrc.js delete mode 100644 packages/datafile-manager/.gitignore delete mode 100644 packages/datafile-manager/.nvmrc delete mode 100644 packages/datafile-manager/.prettierrc delete mode 100644 packages/datafile-manager/CHANGELOG.md delete mode 100644 packages/datafile-manager/LICENSE delete mode 100644 packages/datafile-manager/README.md delete mode 100644 packages/datafile-manager/__mocks__/@react-native-async-storage/async-storage.ts delete mode 100644 packages/datafile-manager/__test__/backoffController.spec.ts delete mode 100644 packages/datafile-manager/__test__/browserDatafileManager.spec.ts delete mode 100644 packages/datafile-manager/__test__/browserRequest.spec.ts delete mode 100644 packages/datafile-manager/__test__/eventEmitter.spec.ts delete mode 100644 packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts delete mode 100644 packages/datafile-manager/__test__/nodeDatafileManager.spec.ts delete mode 100644 packages/datafile-manager/__test__/nodeRequest.spec.ts delete mode 100644 packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts delete mode 100644 packages/datafile-manager/__test__/testUtils.ts delete mode 100644 packages/datafile-manager/jest.config.js delete mode 100644 packages/datafile-manager/package-lock.json delete mode 100644 packages/datafile-manager/package.json delete mode 100644 packages/datafile-manager/src/backoffController.ts delete mode 100644 packages/datafile-manager/src/browserDatafileManager.ts delete mode 100644 packages/datafile-manager/src/browserRequest.ts delete mode 100644 packages/datafile-manager/src/config.ts delete mode 100644 packages/datafile-manager/src/datafileManager.ts delete mode 100644 packages/datafile-manager/src/eventEmitter.ts delete mode 100644 packages/datafile-manager/src/http.ts delete mode 100644 packages/datafile-manager/src/httpPollingDatafileManager.ts delete mode 100644 packages/datafile-manager/src/index.browser.ts delete mode 100644 packages/datafile-manager/src/index.node.ts delete mode 100644 packages/datafile-manager/src/index.react_native.ts delete mode 100644 packages/datafile-manager/src/nodeDatafileManager.ts delete mode 100644 packages/datafile-manager/src/nodeRequest.ts delete mode 100644 packages/datafile-manager/src/persistentKeyValueCache.ts delete mode 100644 packages/datafile-manager/src/reactNativeAsyncStorageCache.ts delete mode 100644 packages/datafile-manager/src/reactNativeDatafileManager.ts delete mode 100644 packages/datafile-manager/tsconfig.json diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 426192b7d..39cc0e7ba 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -109,7 +109,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - package: [ 'packages/utils', 'packages/event-processor', 'packages/logging', 'packages/datafile-manager'] + package: [ 'packages/utils', 'packages/event-processor', 'packages/logging'] steps: - uses: actions/checkout@v3 - name: Move to package ${{ matrix.package }} diff --git a/README.md b/README.md index 2d852c009..f6206a013 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,6 @@ This repository is a monorepo. It houses the main Javascript SDK and its support | Package | Version | Docs | Description | | - | - | - | - | | [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-datafile-manager`](/packages/datafile-manager) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-datafile-manager.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-datafile-manager) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/initialize-sdk-javascript-node#customize-datafile-management-behavior) | (Consolidated*) Datafile Manager for Optimizely SDK | [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | (Consolidated*) Event Batching support for Optimizely SDK | [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK | [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages diff --git a/packages/datafile-manager/.eslintrc.js b/packages/datafile-manager/.eslintrc.js deleted file mode 100644 index dbbba4a4f..000000000 --- a/packages/datafile-manager/.eslintrc.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2020 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - }, - env: { - browser: true, - es6: true, - jest: true, - mocha: true, - node: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier/@typescript-eslint', - - // Prettier should always go last so it can trump any rules above - 'plugin:prettier/recommended', - 'prettier/react', - ], - rules: { - '@typescript-eslint/ban-ts-ignore': 'off', - '@typescript-eslint/camelcase': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off' - }, -}; diff --git a/packages/datafile-manager/.gitignore b/packages/datafile-manager/.gitignore deleted file mode 100644 index a65b41774..000000000 --- a/packages/datafile-manager/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lib diff --git a/packages/datafile-manager/.nvmrc b/packages/datafile-manager/.nvmrc deleted file mode 100644 index e338b8659..000000000 --- a/packages/datafile-manager/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v10 diff --git a/packages/datafile-manager/.prettierrc b/packages/datafile-manager/.prettierrc deleted file mode 100644 index 62301e2e3..000000000 --- a/packages/datafile-manager/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false -} diff --git a/packages/datafile-manager/CHANGELOG.md b/packages/datafile-manager/CHANGELOG.md deleted file mode 100644 index ae7ff4125..000000000 --- a/packages/datafile-manager/CHANGELOG.md +++ /dev/null @@ -1,108 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.9.5] - June 6, 2022 - -### Changed -- Changed typescript types file path from `lib/index.d.ts` to `lib/index.node.d.ts`. - -## [0.9.4] - February 2, 2022 - -### Changed -- Made `@react-native-async-storage/async-storage` an optional peer dependency. - -## [0.9.3] - January 31, 2022 - -### Changed -- Reverted changes in `0.9.2` and made `@react-native-async-storage/async-storage` a peer dependency again. Making it an optional dependency did not work as expected. - -## [0.9.2] - January 28, 2022 - -### Changed -- Made `@react-native-async-storage/async-storage` an optional dependency. - -## [0.9.1] - October 13, 2021 - -### Fixed -- Downgrade version of typescript to `3.8.x` to fix stubbing issue. -- Update version of logging to `0.3.1` to fix stubbing issue. - -## [0.9.0] - October 8, 2021 - -### Changed -- Update `@optimizely/js-sdk-logging` to `0.3.0`. - -## [0.8.1] - May 25, 2021 - -### Fixed - -- Replaced the deprecated `@react-native-community/async-storage` with `@react-native-async-storage/async-storage`. - -## [0.8.0] - September 1, 2020 - -### Changed - -- Modified datafile manager to accept, process, and return the datafile's string representation instead of the datafile object. -- Remove JSON parsing of response received from datafile fetch request - - Responsibility of validating the datafile now solely belongs to the project config manager -- Modified React Native async storage cache and persistent value cache implementation to store strings instead of objects as values. - -## [0.7.0] - July 28, 2020 - -### Changed - -- Move React Native async storage implementation to datafile manager - -## [0.6.0] - June 8, 2020 - -### New Features -- Added support for authenticated datafiles. `NodeDatafileManager` now accepts `datafileAccessToken` to be able to fetch authenticated datafiles. - -## [0.5.0] - April 17, 2020 - -### Breaking Changes -- Removed `StaticDatafileManager` from all top level exports -- Dropped support for Node.js version <8 - -### Fixed - -- Node datafile manager requests use gzip,deflate compression - -## [0.4.0] - June 12, 2019 - -### Changed -- Changed name of top-level exports in index.node.ts and index.browser.ts from `DatafileManager` to `HttpPollingDatafileManager`, to avoid name conflict with `DatafileManager` interface - -## [0.3.0] - May 13, 2019 - -### New Features -- Added 60 second timeout for all requests - -### Changed -- Changed value for node in engines in package.json from >=4.0.0 to >=6.0.0 -- Updated polling behavior: - - Start update interval timer immediately after request - - When update interval timer fires during request, wait until request completes, then immediately start next request -- Remove `timeoutFactory` property from `HTTPPollingDatafileManager` constructor argument object - -## [0.2.0] - April 9, 2019 - -### Changed -- Increase max error count in backoff controller (can now delay requests for up to 512 seconds) [(#17)](https://github.com/optimizely/javascript-sdk-dev/pull/17) -- Change expected format of `urlTemplate` to be sprintf-compatible (`%s` is replaced with `sdkKey`) [(#17)](https://github.com/optimizely/javascript-sdk-dev/pull/17) -- Promise returned from `onReady` is resolved immediately when `datafile` provided in constructor [(#14)](https://github.com/optimizely/javascript-sdk-dev/pull/14) -- Emit update event whenever datafile changes, not only if `autoUpdate` is true [(#14)](https://github.com/optimizely/javascript-sdk-dev/pull/14) - -### Fixed - -- Fix for Node.js requests when `urlTemplate` contains a port [(#18)](https://github.com/optimizely/javascript-sdk-dev/pull/18) - -## [0.1.0] - March 4, 2019 - -Initial release diff --git a/packages/datafile-manager/LICENSE b/packages/datafile-manager/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/datafile-manager/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/datafile-manager/README.md b/packages/datafile-manager/README.md deleted file mode 100644 index db9dc0b7e..000000000 --- a/packages/datafile-manager/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Javascript SDK Datafile Manager - -This package provides datafile manager implementations for Node.js, browsers, and React Native. - -## Requirements -In general, an ES5-compatible environment is required, as well as `Promise` (must be polyfilled if absent). - -Platform-specific minimum supported versions: - -- Node.js: `8` -- React Native: `0.61.5` - -## Installation - -```sh -npm i @optimizely/js-sdk-datafile-manager -``` - -For React Native, installation of peer dependency `@react-native-async-storage/async-storage` is also required: -```sh -npm i @react-native-async-storage/async-storage -``` - -## Usage - -```js -const { HttpPollingDatafileManager } = require('@optimizely/js-sdk-datafile-manager') - -const manager = new HttpPollingDatafileManager({ - sdkKey: '9LCprAQyd1bs1BBXZ3nVji', - autoUpdate: true, - updateInterval: 5000, -}) -manager.start() -manager.onReady().then(() => { - const datafile = manager.get() - console.log('Manager is ready with datafile: ') - console.log(datafile) -}) -manager.on('update', ({ datafile }) => { - console.log('New datafile available: ') - console.log(datafile) -}) -``` - -## Development -- The `lint` package.json script runs ESLint and Prettier. This applies formatting and lint fixes to all `.ts` files in the `src/` directory. -- The `test` package.json script runs our Jest-based test suite. -- The `tsc` package.json script runs the TypeScript compiler to build the final scripts for publishing (into the `lib/` directory). diff --git a/packages/datafile-manager/__mocks__/@react-native-async-storage/async-storage.ts b/packages/datafile-manager/__mocks__/@react-native-async-storage/async-storage.ts deleted file mode 100644 index 32f721144..000000000 --- a/packages/datafile-manager/__mocks__/@react-native-async-storage/async-storage.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export default class AsyncStorage { - static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise((resolve, reject) => { - switch (key) { - case 'keyThatExists': - resolve('{ "name": "Awesome Object" }') - break - case 'keyThatDoesNotExist': - resolve(null) - break - case 'keyWithInvalidJsonObject': - resolve('bad json }') - break - } - }) - } - - static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise<void> { - return Promise.resolve() - } -} diff --git a/packages/datafile-manager/__test__/backoffController.spec.ts b/packages/datafile-manager/__test__/backoffController.spec.ts deleted file mode 100644 index c2208982c..000000000 --- a/packages/datafile-manager/__test__/backoffController.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import BackoffController from '../src/backoffController'; - -describe('backoffController', () => { - describe('getDelay', () => { - it('returns 0 from getDelay if there have been no errors', () => { - const controller = new BackoffController(); - expect(controller.getDelay()).toBe(0); - }); - - it('increases the delay returned from getDelay (up to a maximum value) after each call to countError', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(8000); - expect(controller.getDelay()).toBeLessThan(9000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(16000); - expect(controller.getDelay()).toBeLessThan(17000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(32000); - expect(controller.getDelay()).toBeLessThan(33000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(64000); - expect(controller.getDelay()).toBeLessThan(65000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(128000); - expect(controller.getDelay()).toBeLessThan(129000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(256000); - expect(controller.getDelay()).toBeLessThan(257000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - // Maximum reached - additional errors should not increase the delay further - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - }); - - it('resets the error count when reset is called', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThan(0); - controller.reset(); - expect(controller.getDelay()).toBe(0); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/browserDatafileManager.spec.ts b/packages/datafile-manager/__test__/browserDatafileManager.spec.ts deleted file mode 100644 index 8906495f7..000000000 --- a/packages/datafile-manager/__test__/browserDatafileManager.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import BrowserDatafileManager from '../src/browserDatafileManager'; -import * as browserRequest from '../src/browserRequest'; -import { Headers, AbortableRequest } from '../src/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('browserDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; - beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(browserRequest, 'makeGetRequest'); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - it('calls makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to false for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should not set a timeout for a later update - expect(getTimerCount()).toBe(0); - - await manager.stop(); - }); -}); diff --git a/packages/datafile-manager/__test__/browserRequest.spec.ts b/packages/datafile-manager/__test__/browserRequest.spec.ts deleted file mode 100644 index 3e03eb843..000000000 --- a/packages/datafile-manager/__test__/browserRequest.spec.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @jest-environment jsdom - */ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; -import { makeGetRequest } from '../src/browserRequest'; - -describe('browserRequest', () => { - describe('makeGetRequest', () => { - let mockXHR: FakeXMLHttpRequestStatic; - let xhrs: FakeXMLHttpRequest[]; - beforeEach(() => { - xhrs = []; - mockXHR = fakeXhr.useFakeXMLHttpRequest(); - mockXHR.onCreate = (req): number => xhrs.push(req); - }); - - afterEach(() => { - mockXHR.restore(); - }); - - it('makes a GET request to the argument URL', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - expect(xhrs.length).toBe(1); - const xhr = xhrs[0]; - const { url, method } = xhr; - expect({ url, method }).toEqual({ - url: 'https://cdn.optimizely.com/datafiles/123.json', - method: 'GET', - }); - - xhr.respond(200, {}, '{"foo":"bar"}'); - - await req.responsePromise; - }); - - it('returns a 200 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(200, {}, '{"foo":"bar"}'); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - headers: {}, - body: '{"foo":"bar"}', - }); - }); - - it('returns a 404 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(404, {}, ''); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - headers: {}, - body: '', - }); - }); - - it('includes headers from the headers argument in the request', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/dataifles/123.json', { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - - expect(xhrs.length).toBe(1); - expect(xhrs[0].requestHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:18 GMT'); - - xhrs[0].respond(404, {}, ''); - - await req.responsePromise; - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond( - 200, - { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - '{"foo":"bar"}' - ); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - }); - - it('returns a rejected promise when there is a request error', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - xhrs[0].error(); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('sets a timeout on the request object', () => { - const onCreateMock = jest.fn(); - mockXHR.onCreate = onCreateMock; - makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - expect(onCreateMock).toBeCalledTimes(1); - expect(onCreateMock.mock.calls[0][0].timeout).toBe(60000); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/eventEmitter.spec.ts b/packages/datafile-manager/__test__/eventEmitter.spec.ts deleted file mode 100644 index a4f1c9683..000000000 --- a/packages/datafile-manager/__test__/eventEmitter.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import EventEmitter from '../src/eventEmitter'; - -describe('event_emitter', () => { - describe('on', () => { - let emitter: EventEmitter; - beforeEach(() => { - emitter = new EventEmitter(); - }); - - it('can add a listener for the update event', () => { - const listener = jest.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledTimes(1); - }); - - it('passes the argument from emit to the listener', () => { - const listener = jest.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledWith({ datafile: 'abcd' }); - }); - - it('returns a dispose function that removes the listener', () => { - const listener = jest.fn(); - const disposer = emitter.on('update', listener); - disposer(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener).toBeCalledTimes(0); - }); - - it('can add several listeners for the update event', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - emitter.on('update', listener1); - emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - }); - - it('can add several listeners and remove only some of them', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - const disposer1 = emitter.on('update', listener1); - const disposer2 = emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - disposer1(); - disposer2(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(2); - }); - - it('can add listeners for different events and remove only some of them', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); - const readyDisposer = emitter.on('ready', readyListener); - const updateDisposer = emitter.on('update', updateListener); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(0); - emitter.emit('update', { datafile: 'abcd' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - readyDisposer(); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - emitter.emit('update', { datafile: 'efgh' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - updateDisposer(); - emitter.emit('update', { datafile: 'ijkl' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - }); - - it('can remove all listeners', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); - emitter.on('ready', readyListener); - emitter.on('update', updateListener); - emitter.removeAllListeners(); - emitter.emit('update', { datafile: 'abcd' }); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(0); - expect(updateListener).toBeCalledTimes(0); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts b/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts deleted file mode 100644 index 79b700380..000000000 --- a/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts +++ /dev/null @@ -1,711 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import HttpPollingDatafileManager from '../src/httpPollingDatafileManager'; -import { Headers, AbortableRequest, Response } from '../src/http'; -import { DatafileManagerConfig } from '../src/datafileManager'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; -import PersistentKeyValueCache from '../src/persistentKeyValueCache'; - -jest.mock('../src/backoffController', () => { - return jest.fn().mockImplementation(() => { - const getDelayMock = jest.fn().mockImplementation(() => 0); - return { - getDelay: getDelayMock, - countError: jest.fn(), - reset: jest.fn(), - }; - }); -}); - -import BackoffController from '../src/backoffController'; - -// Test implementation: -// - Does not make any real requests: just resolves with queued responses (tests push onto queuedResponses) -class TestDatafileManager extends HttpPollingDatafileManager { - queuedResponses: (Response | Error)[] = []; - - responsePromises: Promise<Response>[] = []; - - simulateResponseDelay = false; - - // Need these unsued vars for the mock call types to work (being able to check calls) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - makeGetRequest(url: string, headers: Headers): AbortableRequest { - const nextResponse: Error | Response | undefined = this.queuedResponses.pop(); - let responsePromise: Promise<Response>; - if (nextResponse === undefined) { - responsePromise = Promise.reject('No responses queued'); - } else if (nextResponse instanceof Error) { - responsePromise = Promise.reject(nextResponse); - } else { - if (this.simulateResponseDelay) { - // Actual response will have some delay. This is required to get expected behavior for caching. - responsePromise = new Promise(resolve => setTimeout(() => resolve(nextResponse), 50)); - } else { - responsePromise = Promise.resolve(nextResponse); - } - } - this.responsePromises.push(responsePromise); - return { responsePromise, abort: jest.fn() }; - } - - getConfigDefaults(): Partial<DatafileManagerConfig> { - return {}; - } -} - -const testCache: PersistentKeyValueCache = { - get(key: string): Promise<string> { - let val = ''; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<void> { - return Promise.resolve(); - }, -}; - -describe('httpPollingDatafileManager', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - let manager: TestDatafileManager; - afterEach(async () => { - if (manager) { - manager.stop(); - } - jest.clearAllMocks(); - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: true,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ datafile: JSON.stringify({ foo: 'abcd' }), sdkKey: '123', autoUpdate: true }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself, and updates itself again after a timeout', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"fooz": "barz"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - updateFn.mockReset(); - - await advanceTimersByTime(300000); - - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"fooz": "barz"}' }); - expect(JSON.parse(manager.get())).toEqual({ fooz: 'barz' }); - }); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: false,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - datafile: JSON.stringify({ foo: 'abcd' }), - sdkKey: '123', - autoUpdate: false, - }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself once, but does not schedule a future update', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(getTimerCount()).toBe(0); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: true', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 1000, autoUpdate: true }); - }); - - describe('initial state', () => { - it('returns null from get before becoming ready', () => { - expect(manager.get()).toEqual(''); - }); - }); - - describe('started state', () => { - it('passes the default datafile URL to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/123.json'); - await manager.onReady(); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('live updates', () => { - it('sets a timeout to update again after the update interval', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo4": "bar4"}', - headers: {}, - } - ); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await manager.responsePromises[0]; - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('emits update events after live updates', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - - await advanceTimersByTime(1000); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo2": "bar2"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo2: 'bar2' }); - - updateFn.mockReset(); - - await advanceTimersByTime(1000); - await manager.responsePromises[2]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo3": "bar3"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo3: 'bar3' }); - }); - - describe('when the update interval time fires before the request is complete', () => { - it('waits until the request is complete before making the next request', async () => { - let resolveResponsePromise: (resp: Response) => void; - const responsePromise: Promise<Response> = new Promise(res => { - resolveResponsePromise = res; - }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest').mockReturnValueOnce({ - abort() {}, - responsePromise, - }); - - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - resolveResponsePromise!({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - await responsePromise; - await advanceTimersByTime(0); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - it('cancels a pending timeout when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - - expect(getTimerCount()).toBe(1); - manager.stop(); - expect(getTimerCount()).toBe(0); - }); - - it('cancels reactions to a pending fetch when stop is called', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - - await advanceTimersByTime(1000); - - expect(manager.responsePromises.length).toBe(2); - manager.stop(); - await manager.responsePromises[1]; - // Should not have updated datafile since manager was stopped - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('calls abort on the current request if there is a current request when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.start(); - const currentRequest = makeGetRequestSpy.mock.results[0]; - expect(currentRequest.type).toBe('return'); - expect(currentRequest.value.abort).toBeCalledTimes(0); - manager.stop(); - expect(currentRequest.value.abort).toBeCalledTimes(1); - }); - - it('can fail to become ready on the initial request, but succeed after a later polling update', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }, - { - statusCode: 404, - body: '', - headers: {}, - } - ); - - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - // Not ready yet due to first request failed, but should have queued a live update - expect(getTimerCount()).toBe(1); - // Trigger the update, should fetch the next response which should succeed, then we get ready - advanceTimersByTime(1000); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('newness checking', () => { - it('does not update if the response status is 304', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - // First response promise was for the initial 200 response - expect(manager.responsePromises.length).toBe(1); - // Trigger the queued update - await advanceTimersByTime(1000); - // Second response promise is for the 304 response - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - // Since the response was 304, updateFn should not have been called - expect(updateFn).toBeCalledTimes(0); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('sends if-modified-since using the last observed response last-modified', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - manager.start(); - await manager.onReady(); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - const firstCall = makeGetRequestSpy.mock.calls[0]; - const headers = firstCall[1]; - expect(headers).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - }); - }); - - describe('backoff', () => { - it('uses the delay from the backoff controller getDelay method when greater than updateInterval', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - const getDelayMock = BackoffControllerMock.mock.results[0].value.getDelay; - getDelayMock.mockImplementationOnce(() => 5432); - - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // Should not make another request after 1 second because the error should have triggered backoff - advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // But after another 5 seconds, another request should be made - await advanceTimersByTime(5000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('calls countError on the backoff controller when a non-success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls countError on the backoff controller when the response promise rejects', async () => { - manager.queuedResponses.push(new Error('Connection failed')); - manager.start(); - try { - await manager.responsePromises[0]; - } catch (e) { - //empty - } - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls reset on the backoff controller when a success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }); - manager.start(); - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - // Reset is called in start - we want to check that it is also called after the response, so reset the mock here - BackoffControllerMock.mock.results[0].value.reset.mockReset(); - await manager.onReady(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - }); - - it('resets the backoff controller when start is called', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - manager.start(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - try { - await manager.responsePromises[0]; - } catch (e) { - // empty - } - }); - }); - }); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: false', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', autoUpdate: false }); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('does not schedule a live update after ready', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(getTimerCount()).toBe(0); - }); - - // TODO: figure out what's wrong with this test - it.skip('rejects the onReady promise if the initial request promise rejects', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.makeGetRequest = (): AbortableRequest => ({ - abort(): void {}, - responsePromise: Promise.reject(new Error('Could not connect')), - }); - manager.start(); - let didReject = false; - try { - await manager.onReady(); - } catch (e) { - didReject = true; - } - expect(didReject).toBe(true); - }); - }); - - describe('when constructed with sdkKey and a valid urlTemplate', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: '456', - updateInterval: 1000, - urlTemplate: 'https://localhost:5556/datafiles/%s', - }); - }); - - it('uses the urlTemplate to create the url passed to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://localhost:5556/datafiles/456'); - await manager.onReady(); - }); - }); - - describe('when constructed with an update interval below the minimum', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 500, autoUpdate: true }); - }); - - it('uses the default update interval', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - describe('when constructed with a cache implementation having an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('uses cached version of datafile first and resolves the promise while network throws error and no update event is triggered', async () => { - manager.queuedResponses.push(new Error('Connection Error')); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - }); - - it('uses cached datafile, resolves ready promise, fetches new datafile from network and triggers update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(1); - }); - - it('sets newly recieved datafile in to cache', async () => { - const cacheSetSpy = jest.spyOn(testCache, 'set'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(cacheSetSpy.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); - expect(JSON.parse(cacheSetSpy.mock.calls[0][1])).toEqual({ foo: 'bar' }); - }); - }); - - describe('when constructed with a cache implementation without an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatDoesExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('does not find cached datafile, fetches new datafile from network, resolves promise and does not trigger update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await advanceTimersByTime(50); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts b/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts deleted file mode 100644 index 451e478f5..000000000 --- a/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import NodeDatafileManager from '../src/nodeDatafileManager'; -import * as nodeRequest from '../src/nodeRequest'; -import { Headers, AbortableRequest } from '../src/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('nodeDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; - beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(nodeRequest, 'makeGetRequest'); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - it('calls nodeEnvironment.makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls nodeEnvironment.makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to true for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should set a timeout for a later update - expect(getTimerCount()).toBe(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - - await manager.stop(); - }); - - it('uses authenticated default datafile url when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith( - 'https://config.optimizely.com/datafiles/auth/1234.json', - expect.anything() - ); - await manager.stop(); - }); - - it('uses public default datafile url when auth token is not provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://cdn.optimizely.com/datafiles/1234.json', expect.anything()); - await manager.stop(); - }); - - it('adds authorization header with bearer token when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith(expect.anything(), { Authorization: 'Bearer abcdefgh' }); - await manager.stop(); - }); - - it('prefers user provided url template over defaults', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - urlTemplate: 'https://myawesomeurl/', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://myawesomeurl/', expect.anything()); - await manager.stop(); - }); -}); diff --git a/packages/datafile-manager/__test__/nodeRequest.spec.ts b/packages/datafile-manager/__test__/nodeRequest.spec.ts deleted file mode 100644 index 075907113..000000000 --- a/packages/datafile-manager/__test__/nodeRequest.spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import nock from 'nock'; -import zlib from 'zlib'; -import { makeGetRequest } from '../src/nodeRequest'; -import { advanceTimersByTime } from './testUtils'; - -beforeAll(() => { - nock.disableNetConnect(); -}); - -afterAll(() => { - nock.enableNetConnect(); -}); - -describe('nodeEnvironment', () => { - const host = 'https://cdn.optimizely.com/'; - const path = '/datafiles/123.json'; - - afterEach(async () => { - nock.cleanAll(); - }); - - describe('makeGetRequest', () => { - it('returns a 200 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a 404 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(404, ''); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('includes headers from the headers argument in the request', async () => { - const scope = nock(host) - .matchHeader('if-modified-since', 'Fri, 08 Mar 2019 18:57:18 GMT') - .get(path) - .reply(304, ''); - const req = makeGetRequest(`${host}${path}`, { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 304, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('adds an Accept-Encoding request header and unzips a gzipped response body', async () => { - const scope = nock(host) - .matchHeader('accept-encoding', 'gzip,deflate') - .get(path) - .reply(200, () => zlib.gzipSync('{"foo":"bar"}'), { 'content-encoding': 'gzip' }); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toMatchObject({ - statusCode: 200, - body: '{"foo":"bar"}', - }); - scope.done(); - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const scope = nock(host) - .get(path) - .reply( - 200, - { foo: 'bar' }, - { - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - } - ); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - scope.done(); - }); - - it('handles a URL with a query string', async () => { - const pathWithQuery = '/datafiles/123.json?from_my_app=true'; - const scope = nock(host) - .get(pathWithQuery) - .reply(200, { foo: 'bar' }); - const req = makeGetRequest(`${host}${pathWithQuery}`, {}); - await req.responsePromise; - scope.done(); - }); - - it('handles a URL with http protocol (not https)', async () => { - const httpHost = 'http://cdn.optimizely.com/'; - const scope = nock(httpHost) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${httpHost}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a rejected response promise when the URL protocol is unsupported', async () => { - const invalidProtocolUrl = 'ftp://something/datafiles/123.json'; - const req = makeGetRequest(invalidProtocolUrl, {}); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('returns a rejected promise when there is a request error', async () => { - const scope = nock(host) - .get(path) - .replyWithError({ - message: 'Connection error', - code: 'CONNECTION_ERROR', - }); - const req = makeGetRequest(`${host}${path}`, {}); - await expect(req.responsePromise).rejects.toThrow(); - scope.done(); - }); - - it('handles a url with a host and a port', async () => { - const hostWithPort = 'http://datafiles:3000/'; - const path = '/12/345.json'; - const scope = nock(hostWithPort) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${hostWithPort}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - describe('timeout', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.clearAllTimers(); - }); - - it('rejects the response promise and aborts the request when the response is not received before the timeout', async () => { - const scope = nock(host) - .get(path) - .delay(61000) - .reply(200, '{"foo":"bar"}'); - - const abortEventListener = jest.fn(); - let emittedReq: any; - const requestListener = (request: any): void => { - emittedReq = request; - emittedReq.once('abort', abortEventListener); - }; - scope.on('request', requestListener); - - const req = makeGetRequest(`${host}${path}`, {}); - await advanceTimersByTime(60000); - await expect(req.responsePromise).rejects.toThrow(); - expect(abortEventListener).toBeCalledTimes(1); - - scope.done(); - if (emittedReq) { - emittedReq.off('abort', abortEventListener); - } - scope.off('request', requestListener); - }); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts b/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts deleted file mode 100644 index 0401a2065..000000000 --- a/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import ReactNativeAsyncStorageCache from '../src/reactNativeAsyncStorageCache'; - -describe('reactNativeAsyncStorageCache', () => { - let cacheInstance: ReactNativeAsyncStorageCache; - - beforeEach(() => { - cacheInstance = new ReactNativeAsyncStorageCache(); - }); - - describe('get', function() { - it('should return correct string when item is found in cache', function() { - return cacheInstance.get('keyThatExists').then(v => expect(JSON.parse(v)).toEqual({ name: 'Awesome Object' })); - }); - - it('should return empty string if item is not found in cache', function() { - return cacheInstance.get('keyThatDoesNotExist').then(v => expect(v).toEqual('')); - }); - }); - - describe('set', function() { - it('should resolve promise if item was successfully set in the cache', function() { - const testObj = { name: 'Awesome Object' }; - return cacheInstance.set('testKey', JSON.stringify(testObj)); - }); - }); - - describe('contains', function() { - it('should return true if value with key exists', function() { - return cacheInstance.contains('keyThatExists').then(v => expect(v).toBeTruthy()); - }); - - it('should return false if value with key does not exist', function() { - return cacheInstance.contains('keyThatDoesNotExist').then(v => expect(v).toBeFalsy()); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/testUtils.ts b/packages/datafile-manager/__test__/testUtils.ts deleted file mode 100644 index 511f1e515..000000000 --- a/packages/datafile-manager/__test__/testUtils.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export function advanceTimersByTime(waitMs: number): Promise<void> { - const timeoutPromise: Promise<void> = new Promise(res => setTimeout(res, waitMs)); - jest.advanceTimersByTime(waitMs); - return timeoutPromise; -} - -export function getTimerCount(): number { - // Type definition for jest doesn't include this, but it exists - // https://jestjs.io/docs/en/jest-object#jestgettimercount - return (jest as any).getTimerCount(); -} diff --git a/packages/datafile-manager/jest.config.js b/packages/datafile-manager/jest.config.js deleted file mode 100644 index e1a14d3fc..000000000 --- a/packages/datafile-manager/jest.config.js +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// For a detailed explanation regarding each configuration property, visit: -// https://jestjs.io/docs/en/configuration.html - -module.exports = { - // All imported modules in your tests should be mocked automatically - // automock: false, - - // Stop running tests after `n` failures - // bail: 0, - - // Respect "browser" field in package.json when resolving modules - // browser: false, - - // The directory where Jest should store its cached dependency information - // cacheDirectory: "/private/var/folders/rz/km3gywzs7h9c5wq67w1gy9l8dc02_d/T/jest_7f76tp", - - // Automatically clear mock calls and instances between every test - clearMocks: true, - - // Indicates whether the coverage information should be collected while executing the test - // collectCoverage: false, - - // An array of glob patterns indicating a set of files for which coverage information should be collected - // collectCoverageFrom: null, - - // The directory where Jest should output its coverage files - // coverageDirectory: null, - - // An array of regexp pattern strings used to skip coverage collection - // coveragePathIgnorePatterns: [ - // "/node_modules/" - // ], - - // A list of reporter names that Jest uses when writing coverage reports - // coverageReporters: [ - // "json", - // "text", - // "lcov", - // "clover" - // ], - - // An object that configures minimum threshold enforcement for coverage results - // coverageThreshold: null, - - // A path to a custom dependency extractor - // dependencyExtractor: null, - - // Make calling deprecated APIs throw helpful error messages - // errorOnDeprecated: false, - - // Force coverage collection from ignored files usin a array of glob patterns - // forceCoverageMatch: [], - - // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: null, - - // A path to a module which exports an async function that is triggered once after all test suites - // globalTeardown: null, - - // A set of global variables that need to be available in all test environments - // globals: {}, - - // An array of directory names to be searched recursively up from the requiring module's location - // moduleDirectories: [ - // "node_modules" - // ], - - // An array of file extensions your modules use - // moduleFileExtensions: [ - // "js", - // "json", - // "jsx", - // "ts", - // "tsx", - // "node" - // ], - "moduleFileExtensions": [ - "ts", - "js", - "json", - "node" - ], - - // A map from regular expressions to module names that allow to stub out resources with a single module - // moduleNameMapper: {}, - - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], - - // Activates notifications for test results - // notify: false, - - // An enum that specifies notification mode. Requires { notify: true } - // notifyMode: "failure-change", - - // A preset that is used as a base for Jest's configuration - // preset: null, - - // Run tests from one or more projects - // projects: null, - - // Use this configuration option to add custom reporters to Jest - // reporters: undefined, - - // Automatically reset mock state between every test - // resetMocks: false, - - // Reset the module registry before running each individual test - // resetModules: false, - - // A path to a custom resolver - // resolver: null, - - // Automatically restore mock state between every test - // restoreMocks: false, - - // The root directory that Jest should scan for tests and modules within - // rootDir: null, - - // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "<rootDir>" - // ], - - // Allows you to use a custom runner instead of Jest's default test runner - // runner: "jest-runner", - - // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], - - // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: [], - - // A list of paths to snapshot serializer modules Jest should use for snapshot testing - // snapshotSerializers: [], - - // The test environment that will be used for testing - testEnvironment: "node", - - // Options that will be passed to the testEnvironment - // testEnvironmentOptions: {}, - - // Adds a location field to test results - // testLocationInResults: false, - - // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - // testPathIgnorePatterns: [ - // "/node_modules/" - // ], - - // The regexp pattern or array of patterns that Jest uses to detect test files - // testRegex: [], - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$", - - // This option allows the use of a custom results processor - // testResultsProcessor: null, - - // This option allows use of a custom test runner - // testRunner: "jasmine2", - - // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href - // testURL: "http://localhost/", - - // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" - // timers: "real", - - // A map from regular expressions to paths to transformers - // transform: null, - "transform": { - "^.+\\.ts$": "ts-jest" - }, - - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "/node_modules/" - // ], - - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them - // unmockedModulePathPatterns: undefined, - - // Indicates whether each individual test should be reported during the run - // verbose: null, - - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode - // watchPathIgnorePatterns: [], - - // Whether to use watchman for file crawling - // watchman: true, -}; diff --git a/packages/datafile-manager/package-lock.json b/packages/datafile-manager/package-lock.json deleted file mode 100644 index f957b9f3c..000000000 --- a/packages/datafile-manager/package-lock.json +++ /dev/null @@ -1,5355 +0,0 @@ -{ - "name": "@optimizely/js-sdk-datafile-manager", - "version": "0.9.5", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", - "dev": true - }, - "@babel/core": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", - "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.3", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.3", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.2", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-transforms": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", - "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz", - "integrity": "sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", - "dev": true, - "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" - } - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz", - "integrity": "sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", - "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.0.1.tgz", - "integrity": "sha512-50E6nN2F5cAXn1lDljn0gE9F0WFXHYz/u0EeR7sOt4nbRPNli34ckbl6CUDaDABJbHt62DYnyQAIB3KgdzwKDw==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.0.1", - "jest-util": "^27.0.1", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/core": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.0.1.tgz", - "integrity": "sha512-PiCbKSMf6t8PEfY3MAd0Ldn3aJAt5T+UcaFkAfMZ1VZgas35+fXk5uHIjAQHQLNIHZWX19TLv0wWNT03yvrw6w==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/reporters": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.0.1", - "jest-config": "^27.0.1", - "jest-haste-map": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-resolve-dependencies": "^27.0.1", - "jest-runner": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "jest-watcher": "^27.0.1", - "micromatch": "^4.0.4", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "@jest/environment": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.0.1.tgz", - "integrity": "sha512-nG+r3uSs2pOTsdhgt6lUm4ZGJLRcTc6HZIkrFsVpPcdSqEpJehEny9r9y2Bmhkn8fKXWdGCYJKF3i4nKO0HSmA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "jest-mock": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/fake-timers": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.0.1.tgz", - "integrity": "sha512-3CyLJQnHzKI4TCJSCo+I9TzIHjSK4RrNEk93jFM6Q9+9WlSJ3mpMq/p2YuKMe0SiHKbmZOd5G/Ll5ofF9Xkw9g==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@sinonjs/fake-timers": "^7.0.2", - "@types/node": "*", - "jest-message-util": "^27.0.1", - "jest-mock": "^27.0.1", - "jest-util": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/globals": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.0.1.tgz", - "integrity": "sha512-80ZCzgopysKdpp5EOglgjApKxiNDR96PG4PwngB4fTwZ4qqqSKo0EwGwQIhl16szQ1M2xCVYmr9J6KelvnABNQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/types": "^27.0.1", - "expect": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/reporters": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.0.1.tgz", - "integrity": "sha512-lZbJWuS1h/ytKERfu1D6tEQ4PuQ7+15S4+HrSzHR0i7AGVT1WRo49h4fZqxASOp7AQCupUVtPJNZDkaG9ZXy0g==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-util": "^27.0.1", - "jest-worker": "^27.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/source-map": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.1.tgz", - "integrity": "sha512-yMgkF0f+6WJtDMdDYNavmqvbHtiSpwRN2U/W+6uztgfqgkq/PXdKPqjBTUF1RD/feth4rH5N3NW0T5+wIuln1A==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.0.1.tgz", - "integrity": "sha512-5aa+ibX2dsGSDLKaQMZb453MqjJU/CRVumebXfaJmuzuGE4qf87yQ2QZ6PEpEtBwVUEgrJCzi3jLCRaUbksSuw==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/test-sequencer": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.0.1.tgz", - "integrity": "sha512-yK2c2iruJ35WgH4KH8whS72uH+FASJUrzwxzNKTzLAEWmNpWKNEPOsSEKsHynvz78bLHafrTg4adN7RrYNbEOA==", - "dev": true, - "requires": { - "@jest/test-result": "^27.0.1", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.1", - "jest-runner": "^27.0.1", - "jest-runtime": "^27.0.1" - } - }, - "@jest/transform": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.0.1.tgz", - "integrity": "sha512-LC95VpT6wMnQ96dRJDlUiAnW/90zyh4+jS30szI/5AsfS0qwSlr/O4TPcGoD2WVaVMfo6KvR+brvOtGyMHaNhA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.0.1", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-util": "^27.0.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "requires": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.15.4.tgz", - "integrity": "sha512-pC0MS6UBuv/YiVAxtzi7CgUed8oCQNYMtGt0yb/I9fI/BWTiJK5cj4YtW2XtL95K5IuvPX/6uGWaouZ8KqXwdg==", - "dev": true, - "requires": { - "deep-assign": "^3.0.0" - } - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.1.tgz", - "integrity": "sha512-am34LJf0N2nON/PT9G7pauA+xjcwX9P6x31m4hBgfUeSXYRZBRv/R6EcdWs8iV4XJjPO++NTsrj7ua/cN2s6ZA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.14", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", - "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz", - "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "26.0.23", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", - "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", - "dev": true, - "requires": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", - "dev": true - }, - "@types/nock": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@types/nock/-/nock-9.3.1.tgz", - "integrity": "sha512-eOVHXS5RnWOjTVhu3deCM/ruy9E6JCgeix2g7wpFiekQh3AaEAK1cz43tZDukKmtSmQnwvSySq7ubijCA32I7Q==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "11.15.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.54.tgz", - "integrity": "sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g==", - "dev": true - }, - "@types/prettier": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", - "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true - }, - "@types/yargs": { - "version": "15.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", - "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", - "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz", - "integrity": "sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "3.10.1", - "debug": "^4.1.1", - "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", - "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", - "dev": true, - "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.10.1", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-visitor-keys": "^1.1.0" - } - }, - "@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.0.1.tgz", - "integrity": "sha512-aWFD7OGQjk3Y8MdZKf1XePlQvHnjMVJQjIq9WKrlAjz9by703kJ45Jxhp26JwnovoW71YYz5etuqRl8wMcIv0w==", - "dev": true, - "requires": { - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.0.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.1.tgz", - "integrity": "sha512-sqBF0owAcCDBVEDtxqfYr2F36eSHdx7lAVGyYuOBRnKdD6gzcy0I0XrAYCZgOA3CRrLhmR+Uae9nogPzmAtOfQ==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.0.1.tgz", - "integrity": "sha512-nIBIqCEpuiyhvjQs2mVNwTxQQa2xk70p9Dd/0obQGBf8FBzbnI8QhQKzLsWMN2i6q+5B0OcWDtrboBX5gmOLyA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^27.0.1", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001230", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz", - "integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==", - "dev": true - }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.1.tgz", - "integrity": "sha512-jVamGdJPDeuQilKhvVn1h3knuMOZzr8QDnpk+M9aMlCaMkTDd6fBWPhiDqFvFZ07pL0liqabAiuy8SY4jGHeaw==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz", - "integrity": "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "electron-to-chromium": { - "version": "1.3.740", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.740.tgz", - "integrity": "sha512-Mi2m55JrX2BFbNZGKYR+2ItcGnR4O5HhrvgoRRyZQlaMGQULqDhoGkLWHzJoshSzi7k1PUofxcDbNhlFrDZNhg==", - "dev": true - }, - "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-plugin-prettier": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", - "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expect": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.0.1.tgz", - "integrity": "sha512-hjKwLeAvKUiq0Plha1dmzOH1FGEwJC9njbT993cq4PK9r58/+3NM+WDqFVGcPuRH7XTjmbIeHQBzp2faDrPhjQ==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-regex-util": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", - "dev": true, - "requires": { - "ci-info": "^3.1.1" - } - }, - "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.0.1.tgz", - "integrity": "sha512-lFEoUdXjbGAIxk/gZhcv98xOaH1hjqG5R/PQHs5GBfIK5iL3tnXCjHQf4HQLVZZ2rcXML3oeVg9+XrRZbooBdQ==", - "dev": true, - "requires": { - "@jest/core": "^27.0.1", - "import-local": "^3.0.2", - "jest-cli": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "jest-cli": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.0.1.tgz", - "integrity": "sha512-plDsQQwpkKK1SZ5L5xqMa7v/sTwB5LTIeSJqb+cV+4EMlThdUQfg8jwMfHX8jHuUc9TPGLcdoZeBuZcGGn3Rlg==", - "dev": true, - "requires": { - "@jest/core": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "prompts": "^2.0.1", - "yargs": "^16.0.3" - } - } - } - }, - "jest-changed-files": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.0.1.tgz", - "integrity": "sha512-Y/4AnqYNcUX/vVgfkmvSA3t7rcg+t8m3CsSGlU+ra8kjlVW5ZqXcBZY/NUew2Mo8M+dn0ApKl+FmGGT1JV5dVA==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-circus": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.0.1.tgz", - "integrity": "sha512-Tz3ytmrsgxWlTwSyPYb8StF9J2IMjLlbBMKAjhL2UU9/0ZpYb2JiEGjXaAhnGauQRbbpyFbSH3yj5HIbdurmwQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.0.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-runner": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "pretty-format": "^27.0.1", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-config": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.0.1.tgz", - "integrity": "sha512-V8O6+CZjGF0OMq4kxVR29ztV/LQqlAAcJLw7a94RndfRXkha4U84n50yZCXiPWtAHHTmb3g1y52US6rGPxA+3w==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.0.1", - "@jest/types": "^27.0.1", - "babel-jest": "^27.0.1", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "jest-circus": "^27.0.1", - "jest-environment-jsdom": "^27.0.1", - "jest-environment-node": "^27.0.1", - "jest-get-type": "^27.0.1", - "jest-jasmine2": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "micromatch": "^4.0.4", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-docblock": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.1.tgz", - "integrity": "sha512-TA4+21s3oebURc7VgFV4r7ltdIJ5rtBH1E3Tbovcg7AV+oLfD5DcJ2V2vJ5zFA9sL5CFd/d2D6IpsAeSheEdrA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.0.1.tgz", - "integrity": "sha512-uJTK/aZ05HsdKkfXucAT5+/1DIURnTRv34OSxn1HWHrD+xu9eDX5Xgds09QSvg/mU01VS5upuHTDKG3W+r0rQA==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.1", - "jest-util": "^27.0.1", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-environment-jsdom": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.0.1.tgz", - "integrity": "sha512-lesU8T9zkjgLaLpUFmFDgchu6/2OCoXm52nN6UumR063Hb+1TJdI7ihgM86+G01Ay86Lyr+K/FAR6yIIOviH3Q==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/fake-timers": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "jest-mock": "^27.0.1", - "jest-util": "^27.0.1", - "jsdom": "^16.6.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-environment-node": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.0.1.tgz", - "integrity": "sha512-/p94lo0hx+hbKUw1opnRFUPPsjncRBEUU+2Dh7BuxX8Nr4rRiTivLYgXzo79FhaeMYV0uiV5WAbHBq6xC11JJg==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/fake-timers": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "jest-mock": "^27.0.1", - "jest-util": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "jest-haste-map": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.0.1.tgz", - "integrity": "sha512-ioCuobr4z90H1Pz8+apz2vfz63387apzAoawm/9IIOndarDfRkjLURdLOe//AI5jUQmjVRg+WiL92339kqlCmA==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.1", - "jest-serializer": "^27.0.1", - "jest-util": "^27.0.1", - "jest-worker": "^27.0.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-jasmine2": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.0.1.tgz", - "integrity": "sha512-o8Ist0o970QDDm/R2o9UDbvNxq8A0++FTFQ0z9OnieJwS1nDH6H7WBDYAGPTdmnla7kbW41oLFPvhmjJE4mekg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.0.1", - "@jest/source-map": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.0.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "pretty-format": "^27.0.1", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-leak-detector": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.0.1.tgz", - "integrity": "sha512-SQ/lRhfmnV3UuiaKIjwNXCaW2yh1rTMAL4n4Cl4I4gU0X2LoIc6Ogxe4UKM/J6Ld2uzc4gDGVYc5lSdpf6WjYw==", - "dev": true, - "requires": { - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-matcher-utils": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.0.1.tgz", - "integrity": "sha512-NauNU+olKhPzLlsRnTOYFGk/MK5QFYl9ZzkrtfsY4eCq4SB3Bcl03UL44VdnlN5S/uFn4H2jwvRY1y6nSDTX3g==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.0.1", - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.1.tgz", - "integrity": "sha512-XPLijkfJUh/PIBnfkcSHgvD6tlYixmcMAn3osTk6jt+H0v/mgURto1XUiD9DKuGX5NDoVS6dSlA23gd9FUaCFg==", - "dev": true - }, - "jest-diff": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.1.tgz", - "integrity": "sha512-DQ3OgfJgoGWVTYo4qnYW/Jg5mpYFS2QW9BLxA8bs12ZRN1K8QPZtWeYvUPohQFs3CHX3JLTndGg3jyxdL5THFQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.1", - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - } - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-message-util": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.0.1.tgz", - "integrity": "sha512-w8BfON2GwWORkos8BsxcwwQrLkV2s1ENxSRXK43+6yuquDE2hVxES/jrFqOArpP1ETVqqMmktU6iGkG8ncVzeA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-mock": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.0.1.tgz", - "integrity": "sha512-fXCSZQDT5hUcAUy8OBnB018x7JFOMQnz4XfpSKEbfpWzL6o5qaLRhgf2Qg2NPuVKmC/fgOf33Edj8wjF4I24CQ==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/node": "*" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true - }, - "jest-regex-util": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.1.tgz", - "integrity": "sha512-6nY6QVcpTgEKQy1L41P4pr3aOddneK17kn3HJw6SdwGiKfgCGTvH02hVXL0GU8GEKtPH83eD2DIDgxHXOxVohQ==", - "dev": true - }, - "jest-resolve": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.0.1.tgz", - "integrity": "sha512-Q7QQ0OZ7z6D5Dul0MrsexlKalU8ZwexBfHLSu1qYPgphvUm6WO1b/xUnipU3e+uW1riDzMcJeJVYbdQ37hBHeg==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "escalade": "^3.1.1", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.0.1", - "resolve": "^1.20.0", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.1.tgz", - "integrity": "sha512-ly1x5mEf21f3IVWbUNwIz/ePLtv4QdhYuQIVSVDqxx7yzAwhhdu0DJo7UNiEYKQY7Im48wfbNdOUpo7euFUXBQ==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-snapshot": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-runner": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.0.1.tgz", - "integrity": "sha512-DUNizlD2D7J80G3VOrwfbtb7KYxiftMng82HNcKwTW0W3AwwNuBeq+1exoCnLO7Mxh7NP+k/1XQBlzLpjr/CnA==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/environment": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^27.0.1", - "jest-docblock": "^27.0.1", - "jest-haste-map": "^27.0.1", - "jest-leak-detector": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-util": "^27.0.1", - "jest-worker": "^27.0.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-runtime": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.0.1.tgz", - "integrity": "sha512-ImcrbQtpCUp8X9Rm4ky3j1GG9cqIKZJvXGZyB5cHEapGPTmg7wvvNooLmKragEe61/p/bhw1qO68Y0/9BSsBBg==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/environment": "^27.0.1", - "@jest/fake-timers": "^27.0.1", - "@jest/globals": "^27.0.1", - "@jest/source-map": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-mock": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.0.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-serializer": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.1.tgz", - "integrity": "sha512-svy//5IH6bfQvAbkAEg1s7xhhgHTtXu0li0I2fdKHDsLP2P2MOiscPQIENQep8oU2g2B3jqLyxKKzotZOz4CwQ==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.0.1.tgz", - "integrity": "sha512-HgKmSebDB3rswugREeh+nKrxJEVZE12K7lZ2MuwfFZT6YmiH0TlofsL2YmiLsCsG5KH5ZcLYYpF5bDrvtVx/Xg==", - "dev": true, - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.0.1", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.0.1", - "jest-get-type": "^27.0.1", - "jest-haste-map": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-util": "^27.0.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.0.1", - "semver": "^7.3.2" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.1.tgz", - "integrity": "sha512-XPLijkfJUh/PIBnfkcSHgvD6tlYixmcMAn3osTk6jt+H0v/mgURto1XUiD9DKuGX5NDoVS6dSlA23gd9FUaCFg==", - "dev": true - }, - "jest-diff": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.1.tgz", - "integrity": "sha512-DQ3OgfJgoGWVTYo4qnYW/Jg5mpYFS2QW9BLxA8bs12ZRN1K8QPZtWeYvUPohQFs3CHX3JLTndGg3jyxdL5THFQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.1", - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - } - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-util": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.0.1.tgz", - "integrity": "sha512-lEw3waSmEOO4ZkwkUlFSvg4es1+8+LIkSGxp/kF60K0+vMR3Dv3O2HMZhcln9NHqSQzpVbsDT6OeMzUPW7DfRg==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "picomatch": "^2.2.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-validate": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.0.1.tgz", - "integrity": "sha512-zvmPRcfTkqTZuHveIKAI2nbkUc3SDXjWVJULknPLGF5bdxOGSeGZg7f/Uw0MUVOkCOaspcHnsPCgZG0pqmg71g==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.1", - "leven": "^3.1.0", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-watcher": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.0.1.tgz", - "integrity": "sha512-Chp9c02BN0IgEbtGreyAhGqIsOrn9a0XnzbuXOxdW1+cW0Tjh12hMzHDIdLFHpYP/TqaMTmPHaJ5KWvpCCrNFw==", - "dev": true, - "requires": { - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.0.1", - "string-length": "^4.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-worker": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.1.tgz", - "integrity": "sha512-NhHqClI3owOjmS8dBhQMKHZ2rrT0sBTpqGitp9nMX5AAjVXd+15o4v96uBEMhoywaLKN+5opcKBlXwAoADZolA==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.6.0.tgz", - "integrity": "sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.5", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz", - "integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==", - "dev": true - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "requires": { - "mime-db": "1.47.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - } - }, - "nock": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", - "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", - "dev": true, - "requires": { - "chai": "^4.1.2", - "debug": "^4.1.0", - "deep-equal": "^1.0.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.5", - "mkdirp": "^0.5.0", - "propagate": "^1.0.0", - "qs": "^6.5.1", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - }, - "dependencies": { - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - } - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", - "dev": true - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "propagate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", - "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.1.tgz", - "integrity": "sha512-03qAt77QjhxyM5Bt2KrrT1WbdumiwLz989sD3IUznSp3GIFQrx76kQqSMLF7ynnxrF3/1ipzABnHxMlU8PD4Vw==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", - "lodash": "4.x", - "make-error": "1.x", - "mkdirp": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", - "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true - } - } -} diff --git a/packages/datafile-manager/package.json b/packages/datafile-manager/package.json deleted file mode 100644 index 77c534256..000000000 --- a/packages/datafile-manager/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@optimizely/js-sdk-datafile-manager", - "version": "0.9.5", - "description": "Optimizely Full Stack Datafile Manager", - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/datafile-manager" - }, - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - }, - "main": "lib/index.node.js", - "browser": "lib/index.browser.js", - "react-native": "lib/index.react_native.js", - "types": "lib/index.node.d.ts", - "directories": { - "lib": "lib", - "test": "__test__" - }, - "files": [ - "lib" - ], - "publishConfig": { - "access": "public" - }, - "devDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", - "@types/jest": "^26.0.3", - "@types/nise": "^1.4.0", - "@types/nock": "^9.3.1", - "@types/node": "^11.11.7", - "@typescript-eslint/eslint-plugin": "^3.3.0", - "@typescript-eslint/parser": "^3.3.0", - "eslint": "^6.8.0", - "eslint-config-prettier": "^6.10.0", - "eslint-plugin-prettier": "^3.1.2", - "jest": "^27.0.1", - "nise": "^1.4.10", - "nock": "^10.0.6", - "prettier": "^1.19.1", - "ts-jest": "^27.0.1", - "typescript": "^4.7.4" - }, - "dependencies": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0", - "decompress-response": "^4.2.1" - }, - "peerDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - }, - "scripts": { - "clean": "rm -rf lib", - "lint": "tsc --noEmit && eslint --fix 'src/**/*.ts' '__test__/**/*.ts'", - "test": "jest", - "posttest": "npm run lint", - "prebuild": "npm run clean", - "build": "tsc", - "prepare": "npm run build", - "prepublishOnly": "npm test" - } -} diff --git a/packages/datafile-manager/src/backoffController.ts b/packages/datafile-manager/src/backoffController.ts deleted file mode 100644 index 8021f8cbd..000000000 --- a/packages/datafile-manager/src/backoffController.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT } from './config'; - -function randomMilliseconds(): number { - return Math.round(Math.random() * 1000); -} - -export default class BackoffController { - private errorCount = 0; - - getDelay(): number { - if (this.errorCount === 0) { - return 0; - } - const baseWaitSeconds = - BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT[ - Math.min(BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1, this.errorCount) - ]; - return baseWaitSeconds * 1000 + randomMilliseconds(); - } - - countError(): void { - if (this.errorCount < BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1) { - this.errorCount++; - } - } - - reset(): void { - this.errorCount = 0; - } -} diff --git a/packages/datafile-manager/src/browserDatafileManager.ts b/packages/datafile-manager/src/browserDatafileManager.ts deleted file mode 100644 index cc7cb046a..000000000 --- a/packages/datafile-manager/src/browserDatafileManager.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; - -export default class BrowserDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: false, - }; - } -} diff --git a/packages/datafile-manager/src/browserRequest.ts b/packages/datafile-manager/src/browserRequest.ts deleted file mode 100644 index 2165903fc..000000000 --- a/packages/datafile-manager/src/browserRequest.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AbortableRequest, Response, Headers } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import { getLogger } from '@optimizely/js-sdk-logging'; - -const logger = getLogger('DatafileManager'); - -const GET_METHOD = 'GET'; -const READY_STATE_DONE = 4; - -function parseHeadersFromXhr(req: XMLHttpRequest): Headers { - const allHeadersString = req.getAllResponseHeaders(); - - if (allHeadersString === null) { - return {}; - } - - const headerLines = allHeadersString.split('\r\n'); - const headers: Headers = {}; - headerLines.forEach(headerLine => { - const separatorIndex = headerLine.indexOf(': '); - if (separatorIndex > -1) { - const headerName = headerLine.slice(0, separatorIndex); - const headerValue = headerLine.slice(separatorIndex + 2); - if (headerValue.length > 0) { - headers[headerName] = headerValue; - } - } - }); - return headers; -} - -function setHeadersInXhr(headers: Headers, req: XMLHttpRequest): void { - Object.keys(headers).forEach(headerName => { - const header = headers[headerName]; - req.setRequestHeader(headerName, header!); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const req = new XMLHttpRequest(); - - const responsePromise: Promise<Response> = new Promise((resolve, reject) => { - req.open(GET_METHOD, reqUrl, true); - - setHeadersInXhr(headers, req); - - req.onreadystatechange = (): void => { - if (req.readyState === READY_STATE_DONE) { - const statusCode = req.status; - if (statusCode === 0) { - reject(new Error('Request error')); - return; - } - - const headers = parseHeadersFromXhr(req); - const resp: Response = { - statusCode: req.status, - body: req.responseText, - headers, - }; - resolve(resp); - } - }; - - req.timeout = REQUEST_TIMEOUT_MS; - - req.ontimeout = (): void => { - logger.error('Request timed out'); - }; - - req.send(); - }); - - return { - responsePromise, - abort(): void { - req.abort(); - }, - }; -} diff --git a/packages/datafile-manager/src/config.ts b/packages/datafile-manager/src/config.ts deleted file mode 100644 index 7c31d8bd0..000000000 --- a/packages/datafile-manager/src/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const DEFAULT_UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes - -export const MIN_UPDATE_INTERVAL = 1000; - -export const DEFAULT_URL_TEMPLATE = `https://cdn.optimizely.com/datafiles/%s.json`; - -export const DEFAULT_AUTHENTICATED_URL_TEMPLATE = `https://config.optimizely.com/datafiles/auth/%s.json`; - -export const BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT = [0, 8, 16, 32, 64, 128, 256, 512]; - -export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute diff --git a/packages/datafile-manager/src/datafileManager.ts b/packages/datafile-manager/src/datafileManager.ts deleted file mode 100644 index f93c8551f..000000000 --- a/packages/datafile-manager/src/datafileManager.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export interface DatafileUpdate { - datafile: string; -} - -export interface DatafileUpdateListener { - (datafileUpdate: DatafileUpdate): void; -} - -// TODO: Replace this with the one from js-sdk-models -interface Managed { - start(): void; - - stop(): Promise<any>; -} - -export interface DatafileManager extends Managed { - get: () => string; - on: (eventName: string, listener: DatafileUpdateListener) => () => void; - onReady: () => Promise<void>; -} - -export interface DatafileManagerConfig { - autoUpdate?: boolean; - datafile?: string; - sdkKey: string; - updateInterval?: number; - urlTemplate?: string; - cache?: PersistentKeyValueCache; -} - -export interface NodeDatafileManagerConfig extends DatafileManagerConfig { - datafileAccessToken?: string; -} diff --git a/packages/datafile-manager/src/eventEmitter.ts b/packages/datafile-manager/src/eventEmitter.ts deleted file mode 100644 index bf4eee44e..000000000 --- a/packages/datafile-manager/src/eventEmitter.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export type Disposer = () => void; - -export type Listener = (arg?: any) => void; - -interface Listeners { - [index: string]: { - // index is event name - [index: string]: Listener; // index is listener id - }; -} - -export default class EventEmitter { - private listeners: Listeners = {}; - - private listenerId = 1; - - on(eventName: string, listener: Listener): Disposer { - if (!this.listeners[eventName]) { - this.listeners[eventName] = {}; - } - const currentListenerId = String(this.listenerId); - this.listenerId++; - this.listeners[eventName][currentListenerId] = listener; - return (): void => { - if (this.listeners[eventName]) { - delete this.listeners[eventName][currentListenerId]; - } - }; - } - - emit(eventName: string, arg?: any): void { - const listeners = this.listeners[eventName]; - if (listeners) { - Object.keys(listeners).forEach(listenerId => { - const listener = listeners[listenerId]; - listener(arg); - }); - } - } - - removeAllListeners(): void { - this.listeners = {}; - } -} - -// TODO: Create a typed event emitter for use in TS only (not JS) diff --git a/packages/datafile-manager/src/http.ts b/packages/datafile-manager/src/http.ts deleted file mode 100644 index 41503b1aa..000000000 --- a/packages/datafile-manager/src/http.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Headers is the interface that bridges between the abstract datafile manager and - * any Node-or-browser-specific http header types. - * It's simplified and can only store one value per header name. - * We can extend or replace this type if requirements change and we need - * to work with multiple values per header name. - */ -export interface Headers { - [header: string]: string | undefined; -} - -export interface Response { - statusCode?: number; - body: string; - headers: Headers; -} - -export interface AbortableRequest { - abort(): void; - responsePromise: Promise<Response>; -} diff --git a/packages/datafile-manager/src/httpPollingDatafileManager.ts b/packages/datafile-manager/src/httpPollingDatafileManager.ts deleted file mode 100644 index f8232c0bd..000000000 --- a/packages/datafile-manager/src/httpPollingDatafileManager.ts +++ /dev/null @@ -1,339 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '@optimizely/js-sdk-logging'; -import { sprintf } from '@optimizely/js-sdk-utils'; -import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; -import EventEmitter, { Disposer } from './eventEmitter'; -import { AbortableRequest, Response, Headers } from './http'; -import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE } from './config'; -import BackoffController from './backoffController'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -const logger = getLogger('DatafileManager'); - -const UPDATE_EVT = 'update'; - -function isValidUpdateInterval(updateInterval: number): boolean { - return updateInterval >= MIN_UPDATE_INTERVAL; -} - -function isSuccessStatusCode(statusCode: number): boolean { - return statusCode >= 200 && statusCode < 400; -} - -const noOpKeyValueCache: PersistentKeyValueCache = { - get(): Promise<string> { - return Promise.resolve(''); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<void> { - return Promise.resolve(); - }, -}; - -export default abstract class HttpPollingDatafileManager implements DatafileManager { - // Make an HTTP get request to the given URL with the given headers - // Return an AbortableRequest, which has a promise for a Response. - // If we can't get a response, the promise is rejected. - // The request will be aborted if the manager is stopped while the request is in flight. - protected abstract makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest; - - // Return any default configuration options that should be applied - protected abstract getConfigDefaults(): Partial<DatafileManagerConfig>; - - private currentDatafile: string; - - private readonly readyPromise: Promise<void>; - - private isReadyPromiseSettled: boolean; - - private readyPromiseResolver: () => void; - - private readyPromiseRejecter: (err: Error) => void; - - private readonly emitter: EventEmitter; - - private readonly autoUpdate: boolean; - - private readonly updateInterval: number; - - private currentTimeout: any; - - private isStarted: boolean; - - private lastResponseLastModified?: string; - - private datafileUrl: string; - - private currentRequest: AbortableRequest | null; - - private backoffController: BackoffController; - - private cacheKey: string; - - private cache: PersistentKeyValueCache; - - // When true, this means the update interval timeout fired before the current - // sync completed. In that case, we should sync again immediately upon - // completion of the current request, instead of waiting another update - // interval. - private syncOnCurrentRequestComplete: boolean; - - constructor(config: DatafileManagerConfig) { - const configWithDefaultsApplied: DatafileManagerConfig = { - ...this.getConfigDefaults(), - ...config, - }; - const { - datafile, - autoUpdate = false, - sdkKey, - updateInterval = DEFAULT_UPDATE_INTERVAL, - urlTemplate = DEFAULT_URL_TEMPLATE, - cache = noOpKeyValueCache, - } = configWithDefaultsApplied; - - this.cache = cache; - this.cacheKey = 'opt-datafile-' + sdkKey; - this.isReadyPromiseSettled = false; - this.readyPromiseResolver = (): void => {}; - this.readyPromiseRejecter = (): void => {}; - this.readyPromise = new Promise((resolve, reject) => { - this.readyPromiseResolver = resolve; - this.readyPromiseRejecter = reject; - }); - - if (datafile) { - this.currentDatafile = datafile; - if (!sdkKey) { - this.resolveReadyPromise(); - } - } else { - this.currentDatafile = ''; - } - - this.isStarted = false; - - this.datafileUrl = sprintf(urlTemplate, sdkKey); - - this.emitter = new EventEmitter(); - this.autoUpdate = autoUpdate; - if (isValidUpdateInterval(updateInterval)) { - this.updateInterval = updateInterval; - } else { - logger.warn('Invalid updateInterval %s, defaulting to %s', updateInterval, DEFAULT_UPDATE_INTERVAL); - this.updateInterval = DEFAULT_UPDATE_INTERVAL; - } - this.currentTimeout = null; - this.currentRequest = null; - this.backoffController = new BackoffController(); - this.syncOnCurrentRequestComplete = false; - } - - get(): string { - return this.currentDatafile; - } - - start(): void { - if (!this.isStarted) { - logger.debug('Datafile manager started'); - this.isStarted = true; - this.backoffController.reset(); - this.setDatafileFromCacheIfAvailable(); - this.syncDatafile(); - } - } - - stop(): Promise<void> { - logger.debug('Datafile manager stopped'); - this.isStarted = false; - if (this.currentTimeout) { - clearTimeout(this.currentTimeout); - this.currentTimeout = null; - } - - this.emitter.removeAllListeners(); - - if (this.currentRequest) { - this.currentRequest.abort(); - this.currentRequest = null; - } - - return Promise.resolve(); - } - - onReady(): Promise<void> { - return this.readyPromise; - } - - on(eventName: string, listener: (datafileUpdate: DatafileUpdate) => void): Disposer { - return this.emitter.on(eventName, listener); - } - - private onRequestRejected(err: any): void { - if (!this.isStarted) { - return; - } - - this.backoffController.countError(); - - if (err instanceof Error) { - logger.error('Error fetching datafile: %s', err.message, err); - } else if (typeof err === 'string') { - logger.error('Error fetching datafile: %s', err); - } else { - logger.error('Error fetching datafile'); - } - } - - private onRequestResolved(response: Response): void { - if (!this.isStarted) { - return; - } - - if (typeof response.statusCode !== 'undefined' && isSuccessStatusCode(response.statusCode)) { - this.backoffController.reset(); - } else { - this.backoffController.countError(); - } - - this.trySavingLastModified(response.headers); - - const datafile = this.getNextDatafileFromResponse(response); - if (datafile !== '') { - logger.info('Updating datafile from response'); - this.currentDatafile = datafile; - this.cache.set(this.cacheKey, datafile); - if (!this.isReadyPromiseSettled) { - this.resolveReadyPromise(); - } else { - const datafileUpdate: DatafileUpdate = { - datafile, - }; - this.emitter.emit(UPDATE_EVT, datafileUpdate); - } - } - } - - private onRequestComplete(this: HttpPollingDatafileManager): void { - if (!this.isStarted) { - return; - } - - this.currentRequest = null; - - if (!this.isReadyPromiseSettled && !this.autoUpdate) { - // We will never resolve ready, so reject it - this.rejectReadyPromise(new Error('Failed to become ready')); - } - - if (this.autoUpdate && this.syncOnCurrentRequestComplete) { - this.syncDatafile(); - } - this.syncOnCurrentRequestComplete = false; - } - - private syncDatafile(): void { - const headers: Headers = {}; - if (this.lastResponseLastModified) { - headers['if-modified-since'] = this.lastResponseLastModified; - } - - logger.debug('Making datafile request to url %s with headers: %s', this.datafileUrl, () => JSON.stringify(headers)); - this.currentRequest = this.makeGetRequest(this.datafileUrl, headers); - - const onRequestComplete = (): void => { - this.onRequestComplete(); - }; - const onRequestResolved = (response: Response): void => { - this.onRequestResolved(response); - }; - const onRequestRejected = (err: any): void => { - this.onRequestRejected(err); - }; - this.currentRequest.responsePromise - .then(onRequestResolved, onRequestRejected) - .then(onRequestComplete, onRequestComplete); - - if (this.autoUpdate) { - this.scheduleNextUpdate(); - } - } - - private resolveReadyPromise(): void { - this.readyPromiseResolver(); - this.isReadyPromiseSettled = true; - } - - private rejectReadyPromise(err: Error): void { - this.readyPromiseRejecter(err); - this.isReadyPromiseSettled = true; - } - - private scheduleNextUpdate(): void { - const currentBackoffDelay = this.backoffController.getDelay(); - const nextUpdateDelay = Math.max(currentBackoffDelay, this.updateInterval); - logger.debug('Scheduling sync in %s ms', nextUpdateDelay); - this.currentTimeout = setTimeout(() => { - if (this.currentRequest) { - this.syncOnCurrentRequestComplete = true; - } else { - this.syncDatafile(); - } - }, nextUpdateDelay); - } - - private getNextDatafileFromResponse(response: Response): string { - logger.debug('Response status code: %s', response.statusCode); - if (typeof response.statusCode === 'undefined') { - return ''; - } - if (response.statusCode === 304) { - return ''; - } - if (isSuccessStatusCode(response.statusCode)) { - return response.body; - } - return ''; - } - - private trySavingLastModified(headers: Headers): void { - const lastModifiedHeader = headers['last-modified'] || headers['Last-Modified']; - if (typeof lastModifiedHeader !== 'undefined') { - this.lastResponseLastModified = lastModifiedHeader; - logger.debug('Saved last modified header value from response: %s', this.lastResponseLastModified); - } - } - - setDatafileFromCacheIfAvailable(): void { - this.cache.get(this.cacheKey).then(datafile => { - if (this.isStarted && !this.isReadyPromiseSettled && datafile !== '') { - logger.debug('Using datafile from cache'); - this.currentDatafile = datafile; - this.resolveReadyPromise(); - } - }); - } -} diff --git a/packages/datafile-manager/src/index.browser.ts b/packages/datafile-manager/src/index.browser.ts deleted file mode 100644 index 00780202f..000000000 --- a/packages/datafile-manager/src/index.browser.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './browserDatafileManager'; diff --git a/packages/datafile-manager/src/index.node.ts b/packages/datafile-manager/src/index.node.ts deleted file mode 100644 index 7f75e1b25..000000000 --- a/packages/datafile-manager/src/index.node.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './nodeDatafileManager'; diff --git a/packages/datafile-manager/src/index.react_native.ts b/packages/datafile-manager/src/index.react_native.ts deleted file mode 100644 index e6706908d..000000000 --- a/packages/datafile-manager/src/index.react_native.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './reactNativeDatafileManager'; diff --git a/packages/datafile-manager/src/nodeDatafileManager.ts b/packages/datafile-manager/src/nodeDatafileManager.ts deleted file mode 100644 index 1952cb369..000000000 --- a/packages/datafile-manager/src/nodeDatafileManager.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '@optimizely/js-sdk-logging'; -import { makeGetRequest } from './nodeRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { NodeDatafileManagerConfig, DatafileManagerConfig } from './datafileManager'; -import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './config'; - -const logger = getLogger('NodeDatafileManager'); - -export default class NodeDatafileManager extends HttpPollingDatafileManager { - private accessToken?: string; - - constructor(config: NodeDatafileManagerConfig) { - const defaultUrlTemplate = config.datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE; - super({ - ...config, - urlTemplate: config.urlTemplate || defaultUrlTemplate, - }); - this.accessToken = config.datafileAccessToken; - } - - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const requestHeaders = Object.assign({}, headers); - if (this.accessToken) { - logger.debug('Adding Authorization header with Bearer Token'); - requestHeaders['Authorization'] = `Bearer ${this.accessToken}`; - } - return makeGetRequest(reqUrl, requestHeaders); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - }; - } -} diff --git a/packages/datafile-manager/src/nodeRequest.ts b/packages/datafile-manager/src/nodeRequest.ts deleted file mode 100644 index 939bac536..000000000 --- a/packages/datafile-manager/src/nodeRequest.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import http from 'http'; -import https from 'https'; -import url from 'url'; -import { Headers, AbortableRequest, Response } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import decompressResponse from 'decompress-response'; - -// Shared signature between http.request and https.request -type ClientRequestCreator = (options: http.RequestOptions) => http.ClientRequest; - -function getRequestOptionsFromUrl(url: url.UrlWithStringQuery): http.RequestOptions { - return { - hostname: url.hostname, - path: url.path, - port: url.port, - protocol: url.protocol, - }; -} - -/** - * Convert incomingMessage.headers (which has type http.IncomingHttpHeaders) into our Headers type defined in src/http.ts. - * - * Our Headers type is simplified and can't represent mutliple values for the same header name. - * - * We don't currently need multiple values support, and the consumer code becomes simpler if it can assume at-most 1 value - * per header name. - * - */ -function createHeadersFromNodeIncomingMessage(incomingMessage: http.IncomingMessage): Headers { - const headers: Headers = {}; - Object.keys(incomingMessage.headers).forEach(headerName => { - const headerValue = incomingMessage.headers[headerName]; - if (typeof headerValue === 'string') { - headers[headerName] = headerValue; - } else if (typeof headerValue === 'undefined') { - // no value provided for this header - } else { - // array - if (headerValue.length > 0) { - // We don't care about multiple values - just take the first one - headers[headerName] = headerValue[0]; - } - } - }); - return headers; -} - -function getResponseFromRequest(request: http.ClientRequest): Promise<Response> { - // TODO: When we drop support for Node 6, consider using util.promisify instead of - // constructing own Promise - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - request.abort(); - reject(new Error('Request timed out')); - }, REQUEST_TIMEOUT_MS); - - request.once('response', (incomingMessage: http.IncomingMessage) => { - if (request.aborted) { - return; - } - - const response = decompressResponse(incomingMessage); - - response.setEncoding('utf8'); - - let responseData = ''; - response.on('data', (chunk: string) => { - if (!request.aborted) { - responseData += chunk; - } - }); - - response.on('end', () => { - if (request.aborted) { - return; - } - - clearTimeout(timeout); - - resolve({ - statusCode: incomingMessage.statusCode, - body: responseData, - headers: createHeadersFromNodeIncomingMessage(incomingMessage), - }); - }); - }); - - request.on('error', (err: any) => { - clearTimeout(timeout); - - if (err instanceof Error) { - reject(err); - } else if (typeof err === 'string') { - reject(new Error(err)); - } else { - reject(new Error('Request error')); - } - }); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - // TODO: Use non-legacy URL parsing when we drop support for Node 6 - const parsedUrl = url.parse(reqUrl); - - let requester: ClientRequestCreator; - if (parsedUrl.protocol === 'http:') { - requester = http.request; - } else if (parsedUrl.protocol === 'https:') { - requester = https.request; - } else { - return { - responsePromise: Promise.reject(new Error(`Unsupported protocol: ${parsedUrl.protocol}`)), - abort(): void {}, - }; - } - - const requestOptions: http.RequestOptions = { - ...getRequestOptionsFromUrl(parsedUrl), - method: 'GET', - headers: { - ...headers, - 'accept-encoding': 'gzip,deflate', - }, - }; - - const request = requester(requestOptions); - const responsePromise = getResponseFromRequest(request); - - request.end(); - - return { - abort(): void { - request.abort(); - }, - responsePromise, - }; -} diff --git a/packages/datafile-manager/src/persistentKeyValueCache.ts b/packages/datafile-manager/src/persistentKeyValueCache.ts deleted file mode 100644 index 08dfcf1fe..000000000 --- a/packages/datafile-manager/src/persistentKeyValueCache.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys and values. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. string if value found was stored as a string. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<string>; - - /** - * Stores string in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: string): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts b/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts deleted file mode 100644 index c70c10541..000000000 --- a/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<string> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return ''; - } - return val; - }); - } - - set(key: string, val: string): Promise<void> { - return AsyncStorage.setItem(key, val); - } - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/packages/datafile-manager/src/reactNativeDatafileManager.ts b/packages/datafile-manager/src/reactNativeDatafileManager.ts deleted file mode 100644 index a7a2ac44e..000000000 --- a/packages/datafile-manager/src/reactNativeDatafileManager.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache'; - -export default class ReactNativeDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - cache: new ReactNativeAsyncStorageCache(), - }; - } -} diff --git a/packages/datafile-manager/tsconfig.json b/packages/datafile-manager/tsconfig.json deleted file mode 100644 index 9c6e2911b..000000000 --- a/packages/datafile-manager/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src" - ], - "exclude": [ - "./lib" - ] -} diff --git a/packages/optimizely-sdk/README.md b/packages/optimizely-sdk/README.md index 51b86419b..8eae7d1c9 100644 --- a/packages/optimizely-sdk/README.md +++ b/packages/optimizely-sdk/README.md @@ -113,12 +113,11 @@ import optimizely from "npm:@optimizely/optimizely-sdk" ### Packages -This repository is a monorepo. It houses the main Javascript SDK and its supporting packages. +This repository houses the main Javascript SDK and its supporting packages. | Package | Version | Docs | Description | | - | - | - | - | | [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-datafile-manager`](/packages/datafile-manager) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-datafile-manager.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-datafile-manager) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/initialize-sdk-javascript-node#customize-datafile-management-behavior) | (Consolidated*) Datafile Manager for Optimizely SDK | [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | (Consolidated*) Event Batching support for Optimizely SDK | [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK | [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages From e6c2932782a8a2b5495bcf51b04ac58e4bf6b8ea Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 14 Aug 2023 22:13:16 +0600 Subject: [PATCH 031/200] [FSSDK-8219] removed event processor subpackage (#847) --- .github/workflows/javascript.yml | 2 +- README.md | 1 - packages/event-processor/.gitignore | 2 - packages/event-processor/CHANGELOG.md | 117 - packages/event-processor/LICENSE | 202 - .../async-storage.ts | 51 - .../@react-native-community/netinfo.ts | 24 - .../__tests__/buildEventV1.spec.ts | 812 --- .../__tests__/eventQueue.spec.ts | 290 - .../__tests__/pendingEventsDispatcher.spec.ts | 261 - .../__tests__/pendingEventsStore.spec.ts | 143 - .../__tests__/reactNativeEventsStore.spec.ts | 219 - .../__tests__/requestTracker.spec.ts | 64 - .../v1EventProcessor.react_native.spec.ts | 886 --- .../__tests__/v1EventProcessor.spec.ts | 529 -- packages/event-processor/jest.config.js | 15 - packages/event-processor/package-lock.json | 5402 ----------------- packages/event-processor/package.json | 66 - .../event-processor/src/eventDispatcher.ts | 32 - .../event-processor/src/eventProcessor.ts | 81 - packages/event-processor/src/eventQueue.ts | 159 - packages/event-processor/src/events.ts | 101 - .../event-processor/src/index.react_native.ts | 23 - packages/event-processor/src/index.ts | 23 - packages/event-processor/src/managed.ts | 20 - .../src/pendingEventsDispatcher.ts | 88 - .../event-processor/src/pendingEventsStore.ts | 117 - .../src/persistentKeyValueCache.ts | 60 - .../src/reactNativeAsyncStorageCache.ts | 49 - .../src/reactNativeEventsStore.ts | 81 - .../event-processor/src/requestTracker.ts | 60 - packages/event-processor/src/synchronizer.ts | 42 - .../event-processor/src/v1/buildEventV1.ts | 255 - .../src/v1/v1EventProcessor.react_native.ts | 235 - .../src/v1/v1EventProcessor.ts | 100 - packages/event-processor/tsconfig.json | 15 - packages/optimizely-sdk/README.md | 1 - 37 files changed, 1 insertion(+), 10627 deletions(-) delete mode 100644 packages/event-processor/.gitignore delete mode 100644 packages/event-processor/CHANGELOG.md delete mode 100644 packages/event-processor/LICENSE delete mode 100644 packages/event-processor/__mocks__/@react-native-async-storage/async-storage.ts delete mode 100644 packages/event-processor/__mocks__/@react-native-community/netinfo.ts delete mode 100644 packages/event-processor/__tests__/buildEventV1.spec.ts delete mode 100644 packages/event-processor/__tests__/eventQueue.spec.ts delete mode 100644 packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts delete mode 100644 packages/event-processor/__tests__/pendingEventsStore.spec.ts delete mode 100644 packages/event-processor/__tests__/reactNativeEventsStore.spec.ts delete mode 100644 packages/event-processor/__tests__/requestTracker.spec.ts delete mode 100644 packages/event-processor/__tests__/v1EventProcessor.react_native.spec.ts delete mode 100644 packages/event-processor/__tests__/v1EventProcessor.spec.ts delete mode 100644 packages/event-processor/jest.config.js delete mode 100644 packages/event-processor/package-lock.json delete mode 100644 packages/event-processor/package.json delete mode 100644 packages/event-processor/src/eventDispatcher.ts delete mode 100644 packages/event-processor/src/eventProcessor.ts delete mode 100644 packages/event-processor/src/eventQueue.ts delete mode 100644 packages/event-processor/src/events.ts delete mode 100644 packages/event-processor/src/index.react_native.ts delete mode 100644 packages/event-processor/src/index.ts delete mode 100644 packages/event-processor/src/managed.ts delete mode 100644 packages/event-processor/src/pendingEventsDispatcher.ts delete mode 100644 packages/event-processor/src/pendingEventsStore.ts delete mode 100644 packages/event-processor/src/persistentKeyValueCache.ts delete mode 100644 packages/event-processor/src/reactNativeAsyncStorageCache.ts delete mode 100644 packages/event-processor/src/reactNativeEventsStore.ts delete mode 100644 packages/event-processor/src/requestTracker.ts delete mode 100644 packages/event-processor/src/synchronizer.ts delete mode 100644 packages/event-processor/src/v1/buildEventV1.ts delete mode 100644 packages/event-processor/src/v1/v1EventProcessor.react_native.ts delete mode 100644 packages/event-processor/src/v1/v1EventProcessor.ts delete mode 100644 packages/event-processor/tsconfig.json diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 39cc0e7ba..811e78373 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -109,7 +109,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - package: [ 'packages/utils', 'packages/event-processor', 'packages/logging'] + package: [ 'packages/utils', 'packages/logging'] steps: - uses: actions/checkout@v3 - name: Move to package ${{ matrix.package }} diff --git a/README.md b/README.md index f6206a013..9b93f4bb7 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,6 @@ This repository is a monorepo. It houses the main Javascript SDK and its support | Package | Version | Docs | Description | | - | - | - | - | | [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | (Consolidated*) Event Batching support for Optimizely SDK | [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK | [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages diff --git a/packages/event-processor/.gitignore b/packages/event-processor/.gitignore deleted file mode 100644 index d4a125901..000000000 --- a/packages/event-processor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lib/ -doc/ diff --git a/packages/event-processor/CHANGELOG.md b/packages/event-processor/CHANGELOG.md deleted file mode 100644 index 9b8e2a286..000000000 --- a/packages/event-processor/CHANGELOG.md +++ /dev/null @@ -1,117 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.9.5] - February 2, 2022 - -### Changed -- Made these react native peer dependencies optional. - - `@react-native-async-storage/async-storage` - - `@react-native-community/netinfo` - -## [0.9.4] - January 31, 2022 - -### Changed -- Reverted changes in `0.9.3`. Changed these back to peer dependencies. Making them optional did not work as expected. - - `@react-native-async-storage/async-storage` - - `@react-native-community/netinfo` - -## [0.9.3] - January 28, 2022 - -### Changed -- Made these react native dependencies optional. - - `@react-native-async-storage/async-storage` - - `@react-native-community/netinfo` - -## [0.9.2] - November 3, 2021 - -### Fixed -- Impression event should send empty `experimentId` and `variationKey` instead of `null` when no `experiment` or `variation` is found. - -## [0.9.1] - October 13, 2021 - -### Fixed -- Update version of logging to `0.3.1` to fix stubbing issue. - -## [0.9.0] - October 8, 2021 - -### Changed -- Update `@optimizely/js-sdk-logging` to `0.3.0`. - -## [0.8.2] - June 14th, 2021 - -### Fixed -- Update `ConversionEvent` interface to allow event `id` type null and `tags` type undefined. - -## [0.8.1] - May 25th, 2021 - -### Fixed -- Replaced the deprecated `@react-native-community/async-storage` with `@react-native-async-storage/async-storage`. - -## [0.8.0] - November 10th, 2020 - -### New Features -- Update `Visitor.Snapshot` to include `enabled` in decision metadata object to support sending flag decisions. - -## [0.7.0] - October 16th, 2020 - -### New Features -- Update `Visitor.Snapshot` to include metadata object to support sending flag decisions. - -## [0.6.0] - July 28, 2020 - -### Fixed -- Upgrade utils dependency version - -## [0.5.1] - July 22, 2020 - -### Fixed -- In event processor, reverted back the typescript version to fix stubbing issue - -## [0.5.0] - July 21, 2020 - -### New Features -- Added Offline storage support to Event processor for React Native Apps - -## [0.4.0] - February 19, 2020 - -### New Features -- Promise returned from `stop` method of `EventProcessor` now tracks the state of all in-flight dispatcher requests, not just the final request that was triggered at the time `stop` was called - -## [0.3.2] - October 21, 2019 - -- Fixed a runtime error when accessing localstorage in `PendingEventsStore` when SDK is used in a React Native application. Pending events will still not be stored when SDK is used in React Native but no error will be thrown anymore. - -## [0.3.1] - August 29, 2019 - -### Fixed -- `DefaultEventQueue` no longer enqueues additional events after being stopped. As a result, `AbstractEventProcessor` no longer processes events after being stopped. -- `DefaultEventQueue` clears its buffer after being stopped. Event duplication, which was previously possible when additional events were enqueued after the stop, is no longer possible. - -## [0.3.0] - August 13, 2019 - -### New Features -- In `AbstractEventProcessor`, validate `maxQueueSize` and `flushInterval`; ignore & use default values when invalid -- `AbstractEventProcessor` can be constructed with a `notificationCenter`. When `notificationCenter` is provided, it triggers a log event notification after the event is sent to the event dispatcher - -### Changed -- Removed transformers, interceptors, and callbacks from `AbstractEventProcessor` -- Removed grouping events by context and dispatching one event per group at flush time. Instead, only maintain one group and flush immediately when an incompatible event is processed. - -## [0.2.1] - June 6, 2019 - -- Wrap the `callback` in `try/catch` when implementing a custom `eventDispatcher`. This ensures invoking the `callback` will always cleanup any pending retry tasks. - -## [0.2.0] - March 27, 2019 - -- Add `PendingEventsDispatcher` to wrap another EventDispatcher with retry support for -events that did not send successfully due to page navigation - -## [0.1.0] - March 1, 2019 - -Initial release diff --git a/packages/event-processor/LICENSE b/packages/event-processor/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/event-processor/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/event-processor/__mocks__/@react-native-async-storage/async-storage.ts b/packages/event-processor/__mocks__/@react-native-async-storage/async-storage.ts deleted file mode 100644 index 8f9a3a2f3..000000000 --- a/packages/event-processor/__mocks__/@react-native-async-storage/async-storage.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -let items: {[key: string]: string} = {} - -export default class AsyncStorage { - - static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise(resolve => { - setTimeout(() => resolve(items[key] || null), 1) - }) - } - - static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise<void> { - return new Promise((resolve) => { - setTimeout(() => { - items[key] = value - resolve() - }, 1) - }) - } - - static removeItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise(resolve => { - setTimeout(() => { - items[key] && delete items[key] - resolve(null) - }, 1) - }) - } - - static dumpItems(): {[key: string]: string} { - return items - } - - static clearStore(): void { - items = {} - } -} diff --git a/packages/event-processor/__mocks__/@react-native-community/netinfo.ts b/packages/event-processor/__mocks__/@react-native-community/netinfo.ts deleted file mode 100644 index 4a007aab7..000000000 --- a/packages/event-processor/__mocks__/@react-native-community/netinfo.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -let localCallback: any - -export function addEventListener(callback: any) { - localCallback = callback -} - -export function triggerInternetState(isInternetReachable: boolean) { - localCallback({ isInternetReachable }) -} diff --git a/packages/event-processor/__tests__/buildEventV1.spec.ts b/packages/event-processor/__tests__/buildEventV1.spec.ts deleted file mode 100644 index 79d77447f..000000000 --- a/packages/event-processor/__tests__/buildEventV1.spec.ts +++ /dev/null @@ -1,812 +0,0 @@ -/** - * Copyright 2019, 2021, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { - buildConversionEventV1, - buildImpressionEventV1, - makeBatchedEventV1, -} from '../src/v1/buildEventV1' -import { ImpressionEvent, ConversionEvent } from '../src/events' - -describe('buildEventV1', () => { - describe('buildImpressionEventV1', () => { - it('should build an ImpressionEventV1 when experiment and variation are defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build an ImpressionEventV1 when experiment and variation are not defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: null, - }, - - experiment: { - id: null, - key: '', - }, - - variation: { - id: null, - key: '', - }, - - ruleKey: '', - flagKey: 'flagKey1', - ruleType: 'rollout', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: null, - experiment_id: "", - variation_id: "", - metadata: { - flag_key: 'flagKey1', - rule_key: '', - rule_type: 'rollout', - variation_key: '', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: null, - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) - - describe('buildConversionEventV1', () => { - it('should build a ConversionEventV1 when tags object is defined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when tags object is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when event id is null', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: null, - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: null, - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should include revenue and value if they are 0', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - - revenue: 0, - value: 0, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - revenue: 0, - value: 0, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should not include $opt_bot_filtering attribute if context.botFiltering is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - ], - }, - ], - }) - }) - }) - - describe('makeBatchedEventV1', () => { - it('should batch Conversion and Impression events together', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = makeBatchedEventV1([impressionEvent, conversionEvent]) - - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/eventQueue.spec.ts b/packages/event-processor/__tests__/eventQueue.spec.ts deleted file mode 100644 index 71aaa415f..000000000 --- a/packages/event-processor/__tests__/eventQueue.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { DefaultEventQueue, SingleEventQueue } from '../src/eventQueue' - -describe('eventQueue', () => { - beforeEach(() => { - jest.useFakeTimers() - }) - - afterEach(() => { - jest.useRealTimers() - jest.resetAllMocks() - }) - - describe('SingleEventQueue', () => { - it('should immediately invoke the sink function when items are enqueued', () => { - const sinkFn = jest.fn() - const queue = new SingleEventQueue<number>({ - sink: sinkFn, - }) - - queue.start() - - queue.enqueue(1) - - expect(sinkFn).toBeCalledTimes(1) - expect(sinkFn).toHaveBeenLastCalledWith([1]) - - queue.enqueue(2) - expect(sinkFn).toBeCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - }) - }) - - describe('DefaultEventQueue', () => { - it('should treat maxQueueSize = -1 as 1', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: -1, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should treat maxQueueSize = 0 as 1', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 0, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should invoke the sink function when maxQueueSize is reached', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - queue.enqueue(3) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2, 3]) - - queue.enqueue(4) - queue.enqueue(5) - queue.enqueue(6) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([4, 5, 6]) - - queue.stop() - }) - - it('should invoke the sink function when the interval has expired', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - jest.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2]) - - queue.enqueue(3) - jest.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([3]) - - queue.stop() - }) - - it('should invoke the sink function when an item incompatable with the current batch (according to batchComparator) is received', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<string>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - // This batchComparator returns true when the argument strings start with the same letter - batchComparator: (s1, s2) => s1[0] === s2[0] - }) - - queue.start() - - queue.enqueue('a1') - queue.enqueue('a2') - // After enqueuing these strings, both starting with 'a', the sinkFn should not yet be called. Thus far all the items enqueued are - // compatible according to the batchComparator. - expect(sinkFn).not.toHaveBeenCalled() - - // Enqueuing a string starting with 'b' should cause the sinkFn to be called - queue.enqueue('b1') - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith(['a1', 'a2']) - }) - - it('stop() should flush the existing queue and call timer.stop()', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - jest.spyOn(queue.timer, 'stop') - - queue.start() - queue.enqueue(1) - - // stop + start is called when the first item is enqueued - expect(queue.timer.stop).toHaveBeenCalledTimes(1) - - queue.stop() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.stop).toHaveBeenCalledTimes(2) - }) - - it('flush() should clear the current batch', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - jest.spyOn(queue.timer, 'refresh') - - queue.start() - queue.enqueue(1) - queue.flush() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.refresh).toBeCalledTimes(1) - - queue.stop() - }) - - it('stop() should return a promise', () => { - const promise = Promise.resolve() - const sinkFn = jest.fn().mockReturnValue(promise) - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - expect(queue.stop()).toBe(promise) - }) - - it('should start the timer when the first event is put into the queue', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - jest.advanceTimersByTime(99) - queue.enqueue(1) - - jest.advanceTimersByTime(2) - expect(sinkFn).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(98) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - - jest.advanceTimersByTime(500) - // ensure sink function wasnt called again since no events have - // been added - expect(sinkFn).toHaveBeenCalledTimes(1) - - queue.enqueue(2) - - jest.advanceTimersByTime(100) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - - }) - - it('should not enqueue additional events after stop() is called', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 30000, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - queue.start() - queue.enqueue(1) - queue.stop() - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - sinkFn.mockClear() - queue.enqueue(2) - queue.enqueue(3) - queue.enqueue(4) - expect(sinkFn).toBeCalledTimes(0) - }) - }) -}) diff --git a/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts b/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts deleted file mode 100644 index 415afdf11..000000000 --- a/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts +++ /dev/null @@ -1,261 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -jest.mock('@optimizely/js-sdk-utils', () => ({ - __esModule: true, - generateUUID: jest.fn(), - getTimestamp: jest.fn(), - objectValues: jest.requireActual('@optimizely/js-sdk-utils').objectValues, -})) - -import { - LocalStoragePendingEventsDispatcher, - PendingEventsDispatcher, - DispatcherEntry, -} from '../src/pendingEventsDispatcher' -import { EventDispatcher, EventV1Request } from '../src/eventDispatcher' -import { EventV1 } from '../src/v1/buildEventV1' -import { PendingEventsStore, LocalStorageStore } from '../src/pendingEventsStore' -import { generateUUID, getTimestamp } from '@optimizely/js-sdk-utils' - -describe('LocalStoragePendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - - beforeEach(() => { - originalEventDispatcher = { - dispatchEvent: jest.fn(), - } - pendingEventsDispatcher = new LocalStoragePendingEventsDispatcher({ - eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((generateUUID as unknown) as jest.Mock).mockReturnValue('uuid') - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 200 }) - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400}) - }) -}) - -describe('PendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - let store: PendingEventsStore<DispatcherEntry> - - beforeEach(() => { - originalEventDispatcher = { - dispatchEvent: jest.fn(), - } - store = new LocalStorageStore({ - key: 'test', - maxValues: 3, - }) - pendingEventsDispatcher = new PendingEventsDispatcher({ - store, - eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((generateUUID as unknown) as jest.Mock).mockReturnValue('uuid') - }) - - afterEach(() => { - localStorage.clear() - }) - - describe('dispatch', () => { - describe('when the dispatch is successful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - expect(callback).not.toHaveBeenCalled() - - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - const internalCallback = internalDispatchCall[1]({ statusCode: 200 }) - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - - expect(store.values()).toHaveLength(0) - }) - }) - - describe('when the dispatch is unsuccessful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - expect(callback).not.toHaveBeenCalled() - - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400 }) - - expect(store.values()).toHaveLength(0) - }) - }) - }) - - describe('sendPendingEvents', () => { - describe('when no pending events are in the store', () => { - it('should not invoked dispatch', () => { - expect(store.values()).toHaveLength(0) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).not.toHaveBeenCalled() - }) - }) - - describe('when there are multiple pending events in the store', () => { - it('should dispatch all of the pending events, and remove them from store', () => { - expect(store.values()).toHaveLength(0) - - const callback = jest.fn() - const eventV1Request1: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event1' } as unknown) as EventV1, - } - - const eventV1Request2: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event2' } as unknown) as EventV1, - } - - store.set('uuid1', { - uuid: 'uuid1', - timestamp: 1, - request: eventV1Request1, - }) - store.set('uuid2', { - uuid: 'uuid2', - timestamp: 2, - request: eventV1Request2, - }) - - expect(store.values()).toHaveLength(2) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) - - // manually invoke original eventDispatcher callback - const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls - internalDispatchCalls[0][1]({ statusCode: 200 }) - internalDispatchCalls[1][1]({ statusCode: 200 }) - - expect(store.values()).toHaveLength(0) - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/pendingEventsStore.spec.ts b/packages/event-processor/__tests__/pendingEventsStore.spec.ts deleted file mode 100644 index 780b5ae13..000000000 --- a/packages/event-processor/__tests__/pendingEventsStore.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { LocalStorageStore } from '../src/pendingEventsStore' - -type TestEntry = { - uuid: string - timestamp: number - value: string -} - -describe('LocalStorageStore', () => { - let store: LocalStorageStore<TestEntry> - beforeEach(() => { - store = new LocalStorageStore({ - key: 'test_key', - maxValues: 3, - }) - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should get, set and remove items', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('1', { - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.values()).toHaveLength(1) - - store.remove('1') - - expect(store.values()).toHaveLength(0) - }) - - it('should allow replacement of the entire map', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'first' }, - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - ]) - - const newMap: { [key: string]: TestEntry } = {} - store.values().forEach(item => { - newMap[item.uuid] = { - ...item, - value: 'new', - } - }) - store.replace(newMap) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'new' }, - { uuid: '2', timestamp: 2, value: 'new' }, - { uuid: '3', timestamp: 3, value: 'new' }, - ]) - }) - - it(`shouldn't allow more than the configured maxValues, using timestamp to remove the oldest entries`, () => { - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('4', { - uuid: '4', - timestamp: 4, - value: 'fourth', - }) - - expect(store.values()).toEqual([ - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - { uuid: '4', timestamp: 4, value: 'fourth' }, - ]) - }) -}) diff --git a/packages/event-processor/__tests__/reactNativeEventsStore.spec.ts b/packages/event-processor/__tests__/reactNativeEventsStore.spec.ts deleted file mode 100644 index d2aff8500..000000000 --- a/packages/event-processor/__tests__/reactNativeEventsStore.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { ReactNativeEventsStore } from '../src/reactNativeEventsStore' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' - -const STORE_KEY = 'test-store' - -describe('ReactNativeEventsStore', () => { - let store: ReactNativeEventsStore<any> - - beforeEach(() => { - store = new ReactNativeEventsStore(5, STORE_KEY) - }) - - describe('set', () => { - it('should store all the events correctly in the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should store all the events when set asynchronously', async (done) => { - const promises = [] - promises.push(store.set('event1', {'name': 'event1'})) - promises.push(store.set('event2', {'name': 'event2'})) - promises.push(store.set('event3', {'name': 'event3'})) - promises.push(store.set('event4', {'name': 'event4'})) - Promise.all(promises).then(() => { - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - done() - }) - }) - }) - - describe('get', () => { - it('should correctly get items', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - expect(await store.get('event1')).toEqual({'name': 'event1'}) - expect(await store.get('event2')).toEqual({'name': 'event2'}) - expect(await store.get('event3')).toEqual({'name': 'event3'}) - expect(await store.get('event4')).toEqual({'name': 'event4'}) - }) - }) - - describe('getEventsMap', () => { - it('should get the whole map correctly', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const mapResult = await store.getEventsMap() - expect(mapResult).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - }) - - describe('getEventsList', () => { - it('should get all the events as a list', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const listResult = await store.getEventsList() - expect(listResult).toEqual([ - { "name": "event1" }, - { "name": "event2" }, - { "name": "event3" }, - { "name": "event4" }, - ]) - }) - }) - - describe('remove', () => { - it('should correctly remove items from the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event1') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event2') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should correctly remove items from the store when removed asynchronously', async (done) => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - const promises = [] - promises.push(store.remove('event1')) - promises.push(store.remove('event2')) - promises.push(store.remove('event3')) - Promise.all(promises).then(() => { - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ "event4": { "name": "event4" }}) - done() - }) - }) - }) - - describe('clear', () => { - it('should clear the whole store',async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.clear() - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY] || '{}') - expect(storedPendingEvents).toEqual({}) - }) - }) - - describe('maxSize', () => { - it('should not add anymore events if the store if full', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.set('event5', {'name': 'event5'}) - - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - - await store.set('event6', {'name': 'event6'}) - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/requestTracker.spec.ts b/packages/event-processor/__tests__/requestTracker.spec.ts deleted file mode 100644 index f6318de50..000000000 --- a/packages/event-processor/__tests__/requestTracker.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import RequestTracker from '../src/requestTracker' - -describe('requestTracker', () => { - describe('onRequestsComplete', () => { - it('returns an immediately-fulfilled promise when no requests are in flight', async () => { - const tracker = new RequestTracker() - await tracker.onRequestsComplete() - }) - - it('returns a promise that fulfills after in-flight requests are complete', async () => { - let resolveReq1: () => void - const req1 = new Promise<void>(resolve => { - resolveReq1 = resolve - }) - let resolveReq2: () => void - const req2 = new Promise<void>(resolve => { - resolveReq2 = resolve - }) - let resolveReq3: () => void - const req3 = new Promise<void>(resolve => { - resolveReq3 = resolve - }) - - const tracker = new RequestTracker() - tracker.trackRequest(req1) - tracker.trackRequest(req2) - tracker.trackRequest(req3) - - let reqsComplete = false - const reqsCompletePromise = tracker.onRequestsComplete().then(() => { - reqsComplete = true - }) - - resolveReq1!() - await req1 - expect(reqsComplete).toBe(false) - - resolveReq2!() - await req2 - expect(reqsComplete).toBe(false) - - resolveReq3!() - await req3 - await reqsCompletePromise - expect(reqsComplete).toBe(true) - }) - }) -}) diff --git a/packages/event-processor/__tests__/v1EventProcessor.react_native.spec.ts b/packages/event-processor/__tests__/v1EventProcessor.react_native.spec.ts deleted file mode 100644 index abc1370cd..000000000 --- a/packages/event-processor/__tests__/v1EventProcessor.react_native.spec.ts +++ /dev/null @@ -1,886 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> -import { NotificationCenter, NOTIFICATION_TYPES } from '@optimizely/js-sdk-utils' - -import { LogTierV1EventProcessor } from '../src/v1/v1EventProcessor.react_native' -import { - EventDispatcher, - EventV1Request, - EventDispatcherCallback, -} from '../src/eventDispatcher' -import { EventProcessor, ProcessableEvent } from '../src/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../src/v1/buildEventV1' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' -import { triggerInternetState } from '../__mocks__/@react-native-community/netinfo' -import { DefaultEventQueue } from '../src/eventQueue' - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessorReactNative', () => { - describe('New Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - - beforeEach(() => { - dispatchStub = jest.fn() - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - }) - - afterEach(() => { - jest.resetAllMocks() - AsyncStorage.clearStore() - }) - - describe('stop()', () => { - let localCallback: EventDispatcherCallback - beforeEach(async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - await processor.stop() - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', async (done) => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - processor.stop().then(() => { - done() - }) - - localCallback({ statusCode: 200 }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', async (done) => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - processor.stop().then(() => { - done() - }) - - localCallback({ statusCode: 400 }) - }) - - it('should return a promise when multiple event batches are sent', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 150)) - await processor.stop() - expect(dispatchStub).toBeCalledTimes(2) - }) - - it('should stop accepting events after stop is called', async () => { - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - await new Promise(resolve => setTimeout(resolve, 150)) - - await processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should immediately flush events as they are processed', async () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 300', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', async () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the queue when the flush interval happens', async () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - await new Promise(resolve => setTimeout(resolve, 350)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - }) - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', async () => { - const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() - } - - const notificationCenter: NotificationCenter = { - sendNotifications: jest.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid batchSize', () => { - it('should ignore a batchSize of 0 and use the default', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) - }) - - describe('Pending Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - - beforeEach(() => { - dispatchStub = jest.fn() - }) - - afterEach(() => { - jest.clearAllMocks() - AsyncStorage.clearStore() - }) - - describe('Retry Pending Events', () => { - describe('App start', () => { - it('should dispatch all the pending events in correct order', async () => { - let receivedEvents: EventV1Request[] = [] - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - let event3 = createConversionEvent() - event3.user.id = 'user3' - let event4 = createConversionEvent() - event4.user.id = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - - jest.clearAllMocks() - - receivedEvents = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - receivedEvents.push(event) - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - - receivedEvents.forEach((e, i) => { - expect(e.params.visitors[0].visitor_id).toEqual(`user${i+1}`) - }) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - }) - - it('should process all the events left in buffer when the app closed last time', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 1000, - batchSize: 4, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - event2.uuid = 'user2' - - processor.process(event1) - processor.process(event2) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Explicitly stopping the timer to simulate app close - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - let receivedEvents: EventV1Request[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - receivedEvents.push(event) - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 4, - }) - - await processor.start() - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toBeCalledTimes(1) - expect(receivedEvents.length).toEqual(1) - const receivedEvent = receivedEvents[0] - - receivedEvent.params.visitors.forEach((v, i) => { - expect(v.visitor_id).toEqual(`user${i+1}`) - }) - - await processor.stop() - }) - - it('should dispatch pending events first and then process events in buffer store', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - - await processor.start() - - for (let i = 0; i < 8; i++) { - let event = createConversionEvent() - event.user.id = `user${i}` - event.uuid = `user${i}` - processor.process(event) - } - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toBeCalledTimes(2) - - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - jest.clearAllMocks() - - const visitorIds: string[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - event.params.visitors.forEach(visitor => visitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 200, - batchSize: 3, - }) - - await processor.start() - - expect(dispatchStub).toBeCalledTimes(2) - - await new Promise(resolve => setTimeout(resolve, 250)) - expect(visitorIds.length).toEqual(8) - expect(visitorIds).toEqual(['user0', 'user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7']) - }) - }) - - describe('When a new event is dispatched', () => { - it('should dispatch all the pending events first and then new event in correct order', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - let event5 = createConversionEvent() - event5.user.id = event5.uuid = 'user5' - - processor.process(event5) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(5) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4', 'user5']) - await processor.stop() - }) - - it('should skip dispatching subsequent events if an event fails to dispatch', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(1) - - processor.process(event2) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(2) - - processor.process(event3) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(3) - - processor.process(event4) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - - expect(dispatchCount).toEqual(4) - - // subsequent events were skipped with each attempt because of request failure - expect(receivedVisitorIds).toEqual(['user1', 'user1', 'user1', 'user1']) - await processor.stop() - }) - }) - - describe('When internet connection is restored', () => { - it('should dispatch all the pending events in correct order when internet connection is restored', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 50)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - triggerInternetState(true) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - - it('should not dispatch duplicate events if internet is lost and restored twice in a short interval', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/v1EventProcessor.spec.ts b/packages/event-processor/__tests__/v1EventProcessor.spec.ts deleted file mode 100644 index e93291e94..000000000 --- a/packages/event-processor/__tests__/v1EventProcessor.spec.ts +++ /dev/null @@ -1,529 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { LogTierV1EventProcessor } from '../src/v1/v1EventProcessor' -import { - EventDispatcher, - EventV1Request, - EventDispatcherCallback, -} from '../src/eventDispatcher' -import { EventProcessor } from '../src/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../src/v1/buildEventV1' -import { NotificationCenter, NOTIFICATION_TYPES } from '@optimizely/js-sdk-utils'; - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessor', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - // TODO change this to ProjectConfig when js-sdk-models is available - let testProjectConfig: any - - beforeEach(() => { - jest.useFakeTimers() - - testProjectConfig = {} - dispatchStub = jest.fn() - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - describe('stop()', () => { - let localCallback: EventDispatcherCallback - beforeEach(() => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - processor.stop().then(() => { - done() - }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - - localCallback({ statusCode: 200 }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', done => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - - localCallback({ - statusCode: 400, - }) - }) - - it('should return a promise when multiple event batches are sent', done => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - processor.stop().then(() => { - expect(dispatchStub).toBeCalledTimes(2) - done() - }) - }) - - it('should stop accepting events after stop is called', () => { - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - - it('should resolve the stop promise after all dispatcher requests are done', async () => { - const dispatchCbs: Array<EventDispatcherCallback> = [] - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - dispatchCbs.push(callback) - }) - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 2, - }) - processor.start() - - for (let i = 0; i < 4; i++) { - processor.process(createImpressionEvent()) - } - expect(dispatchCbs.length).toBe(2) - - let stopPromiseResolved = false - const stopPromise = processor.stop().then(() => { - stopPromiseResolved = true - }) - expect(stopPromiseResolved).toBe(false) - - dispatchCbs[0]({ statusCode: 204 }) - jest.advanceTimersByTime(100) - expect(stopPromiseResolved).toBe(false) - dispatchCbs[1]({ statusCode: 204 }) - await stopPromise - expect(stopPromiseResolved).toBe(true) - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should immediately flush events as they are processed', () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 100', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the queue when the flush interval happens', () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - jest.advanceTimersByTime(100) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - }) - - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', () => { - const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() - } - - const notificationCenter: NotificationCenter = { - sendNotifications: jest.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid flushInterval or batchSize', () => { - it('should ignore a flushInterval of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 0, - batchSize: 10, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(30000) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - }) - - it('should ignore a batchSize of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) -}) diff --git a/packages/event-processor/jest.config.js b/packages/event-processor/jest.config.js deleted file mode 100644 index b862d4238..000000000 --- a/packages/event-processor/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], - "setupFiles": ["jest-localstorage-mock"] -} \ No newline at end of file diff --git a/packages/event-processor/package-lock.json b/packages/event-processor/package-lock.json deleted file mode 100644 index 5d7c4fe62..000000000 --- a/packages/event-processor/package-lock.json +++ /dev/null @@ -1,5402 +0,0 @@ -{ - "name": "@optimizely/js-sdk-event-processor", - "version": "0.9.5", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } - } - }, - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "requires": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.15.4.tgz", - "integrity": "sha512-pC0MS6UBuv/YiVAxtzi7CgUed8oCQNYMtGt0yb/I9fI/BWTiJK5cj4YtW2XtL95K5IuvPX/6uGWaouZ8KqXwdg==", - "dev": true, - "requires": { - "deep-assign": "^3.0.0" - } - }, - "@react-native-community/netinfo": { - "version": "5.9.5", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.5.tgz", - "integrity": "sha512-PbSsRmhRwYIMdeVJTf9gJtvW0TVq/hmgz1xyjsrTIsQ7QS7wbMEiv1Eb/M/y6AEEsdUped5Axm5xykq9TGISHg==", - "dev": true - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "24.9.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.9.1.tgz", - "integrity": "sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q==", - "dev": true, - "requires": { - "jest-diff": "^24.3.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true - }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", - "dev": true - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - } - } - }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true - }, - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", - "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", - "dev": true, - "requires": { - "rsvp": "^3.3.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz", - "integrity": "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", - "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", - "dev": true - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "exec-sh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", - "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, - "requires": { - "merge": "^1.2.0" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "expect": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", - "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^23.6.0", - "jest-get-type": "^22.1.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, - "jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", - "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", - "dev": true, - "requires": { - "import-local": "^1.0.0", - "jest-cli": "^23.6.0" - }, - "dependencies": { - "jest-cli": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", - "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.3.1", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-source-maps": "^1.2.4", - "jest-changed-files": "^23.4.2", - "jest-config": "^23.6.0", - "jest-environment-jsdom": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve-dependencies": "^23.6.0", - "jest-runner": "^23.6.0", - "jest-runtime": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "jest-watcher": "^23.4.0", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "node-notifier": "^5.2.1", - "prompts": "^0.1.9", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^1.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", - "which": "^1.2.12", - "yargs": "^11.0.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - } - } - }, - "jest-changed-files": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", - "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", - "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^23.6.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^23.4.0", - "jest-environment-node": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^23.6.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" - } - }, - "jest-docblock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", - "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", - "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", - "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0" - } - }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", - "dev": true - }, - "jest-haste-map": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", - "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", - "dev": true, - "requires": { - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", - "invariant": "^2.2.4", - "jest-docblock": "^23.2.0", - "jest-serializer": "^23.0.1", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "sane": "^2.0.0" - } - }, - "jest-jasmine2": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", - "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", - "dev": true, - "requires": { - "babel-traverse": "^6.0.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^23.6.0", - "is-generator-fn": "^1.0.0", - "jest-diff": "^23.6.0", - "jest-each": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-leak-detector": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", - "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", - "dev": true, - "requires": { - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-localstorage-mock": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.2.tgz", - "integrity": "sha512-bywlhvs7RM2vpZ0EN12XOU5C2WAXRUbxl1VOxel4cqRUHD6zaUmh99UzwOTx0fWuqjfd0y/NDvEbewNaJaz+UQ==", - "dev": true - }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", - "dev": true - }, - "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", - "dev": true - }, - "jest-resolve": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", - "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "realpath-native": "^1.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", - "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", - "dev": true, - "requires": { - "jest-regex-util": "^23.3.0", - "jest-snapshot": "^23.6.0" - } - }, - "jest-runner": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", - "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-docblock": "^23.2.0", - "jest-haste-map": "^23.6.0", - "jest-jasmine2": "^23.6.0", - "jest-leak-detector": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-runtime": "^23.6.0", - "jest-util": "^23.4.0", - "jest-worker": "^23.2.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", - "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-plugin-istanbul": "^4.1.6", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "exit": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^11.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "jest-serializer": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", - "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=", - "dev": true - }, - "jest-snapshot": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", - "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", - "dev": true, - "requires": { - "babel-types": "^6.0.0", - "chalk": "^2.0.1", - "jest-diff": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-resolve": "^23.6.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^23.6.0", - "semver": "^5.5.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", - "mkdirp": "^0.5.1", - "slash": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-watcher": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", - "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "string-length": "^2.0.0" - } - }, - "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", - "dev": true, - "requires": { - "merge-stream": "^1.0.1" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-notifier": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", - "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "prompts": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", - "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", - "dev": true, - "requires": { - "kleur": "^2.0.1", - "sisteransi": "^0.1.1" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "dev": true, - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", - "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", - "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "capture-exit": "^1.2.0", - "exec-sh": "^0.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.3", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "sisteransi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", - "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "test-exclude": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", - "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^2.3.11", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", - "dev": true - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "ts-jest": { - "version": "23.10.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz", - "integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" - }, - "dependencies": { - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typescript": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", - "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", - "dev": true - }, - "uglify-js": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", - "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", - "dev": true, - "optional": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", - "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } -} diff --git a/packages/event-processor/package.json b/packages/event-processor/package.json deleted file mode 100644 index 151f06960..000000000 --- a/packages/event-processor/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "@optimizely/js-sdk-event-processor", - "version": "0.9.5", - "description": "Optimizely Full Stack Event Processor", - "author": "jordangarcia <jordan@optimizely.com>", - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/event-processor", - "license": "Apache-2.0", - "main": "lib/index.js", - "react-native": "lib/index.react_native.js", - "types": "lib/index.d.ts", - "directories": { - "lib": "lib", - "test": "test" - }, - "files": [ - "lib" - ], - "scripts": { - "clean": "rm -rf lib", - "prebuild": "npm run clean", - "build": "tsc", - "test": "jest", - "prepare": "npm run build", - "prepublishOnly": "npm test" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/event-processor" - }, - "keywords": [ - "optimizely" - ], - "bugs": { - "url": "https://github.com/optimizely/javascript-sdk/issues" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0" - }, - "devDependencies": { - "@react-native-community/netinfo": "^5.9.4", - "@react-native-async-storage/async-storage": "^1.2.0", - "@types/jest": "^24.0.9", - "jest": "^23.6.0", - "jest-localstorage-mock": "^2.4.0", - "ts-jest": "^23.10.5", - "typescript": "^4.7.4" - - }, - "peerDependencies": { - "@react-native-community/netinfo": "5.9.4", - "@react-native-async-storage/async-storage": "^1.2.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - }, - "@react-native-community/netinfo": { - "optional": true - } - } -} diff --git a/packages/event-processor/src/eventDispatcher.ts b/packages/event-processor/src/eventDispatcher.ts deleted file mode 100644 index 28f9ae07c..000000000 --- a/packages/event-processor/src/eventDispatcher.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { EventV1 } from "./v1/buildEventV1"; - -export type EventDispatcherResponse = { - statusCode: number -} - -export type EventDispatcherCallback = (response: EventDispatcherResponse) => void - -export interface EventDispatcher { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void -} - -export interface EventV1Request { - url: string - httpVerb: 'POST' | 'PUT' | 'GET' | 'PATCH' - params: EventV1, -} \ No newline at end of file diff --git a/packages/event-processor/src/eventProcessor.ts b/packages/event-processor/src/eventProcessor.ts deleted file mode 100644 index 57826e41e..000000000 --- a/packages/event-processor/src/eventProcessor.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' -import { ConversionEvent, ImpressionEvent } from './events' -import { EventV1Request } from './eventDispatcher' -import { EventQueue, DefaultEventQueue, SingleEventQueue } from './eventQueue' -import { getLogger } from '@optimizely/js-sdk-logging' -import { NOTIFICATION_TYPES, NotificationCenter } from '@optimizely/js-sdk-utils' - -export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s -export const DEFAULT_BATCH_SIZE = 10 - -const logger = getLogger('EventProcessor') - -export type ProcessableEvent = ConversionEvent | ImpressionEvent - -export type EventDispatchResult = { result: boolean; event: ProcessableEvent } - -export interface EventProcessor extends Managed { - process(event: ProcessableEvent): void -} - -export function validateAndGetFlushInterval(flushInterval: number): number { - if (flushInterval <= 0) { - logger.warn( - `Invalid flushInterval ${flushInterval}, defaulting to ${DEFAULT_FLUSH_INTERVAL}`, - ) - flushInterval = DEFAULT_FLUSH_INTERVAL - } - return flushInterval -} - -export function validateAndGetBatchSize(batchSize: number): number { - batchSize = Math.floor(batchSize) - if (batchSize < 1) { - logger.warn( - `Invalid batchSize ${batchSize}, defaulting to ${DEFAULT_BATCH_SIZE}`, - ) - batchSize = DEFAULT_BATCH_SIZE - } - batchSize = Math.max(1, batchSize) - return batchSize -} - -export function getQueue(batchSize: number, flushInterval: number, sink: any, batchComparator: any): EventQueue<ProcessableEvent> { - let queue: EventQueue<ProcessableEvent> - if (batchSize > 1) { - queue = new DefaultEventQueue<ProcessableEvent>({ - flushInterval, - maxQueueSize: batchSize, - sink, - batchComparator, - }) - } else { - queue = new SingleEventQueue({ sink }) - } - return queue -} - -export function sendEventNotification(notificationCenter: NotificationCenter | undefined, event: EventV1Request): void { - if (notificationCenter) { - notificationCenter.sendNotifications( - NOTIFICATION_TYPES.LOG_EVENT, - event, - ) - } -} diff --git a/packages/event-processor/src/eventQueue.ts b/packages/event-processor/src/eventQueue.ts deleted file mode 100644 index 8a9438a83..000000000 --- a/packages/event-processor/src/eventQueue.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '@optimizely/js-sdk-logging' -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' - -const logger = getLogger('EventProcessor') - -export type EventQueueSink<K> = (buffer: K[]) => Promise<any> - -export interface EventQueue<K> extends Managed { - enqueue(event: K): void -} - -export interface EventQueueFactory<K> { - createEventQueue(config: { - sink: EventQueueSink<K> - flushInterval: number - maxQueueSize: number - }): EventQueue<K> -} - -class Timer { - private timeout: number - private callback: () => void - private timeoutId?: number - - constructor({ timeout, callback }: { timeout: number; callback: () => void }) { - this.timeout = Math.max(timeout, 0) - this.callback = callback - } - - start(): void { - this.timeoutId = setTimeout(this.callback, this.timeout) as any - } - - refresh(): void { - this.stop() - this.start() - } - - stop(): void { - if (this.timeoutId) { - clearTimeout(this.timeoutId as any) - } - } -} - -export class SingleEventQueue<K> implements EventQueue<K> { - private sink: EventQueueSink<K> - - constructor({ sink }: { sink: EventQueueSink<K> }) { - this.sink = sink - } - - start(): void { - // no-op - } - - stop(): Promise<any> { - // no-op - return Promise.resolve() - } - - enqueue(event: K): void { - this.sink([event]) - } -} - -export class DefaultEventQueue<K> implements EventQueue<K> { - // expose for testing - public timer: Timer - private buffer: K[] - private maxQueueSize: number - private sink: EventQueueSink<K> - // batchComparator is called to determine whether two events can be included - // together in the same batch - private batchComparator: (eventA: K, eventB: K) => boolean - private started: boolean - - constructor({ - flushInterval, - maxQueueSize, - sink, - batchComparator, - }: { - flushInterval: number - maxQueueSize: number - sink: EventQueueSink<K> - batchComparator: (eventA: K, eventB: K) => boolean - }) { - this.buffer = [] - this.maxQueueSize = Math.max(maxQueueSize, 1) - this.sink = sink - this.batchComparator = batchComparator - this.timer = new Timer({ - callback: this.flush.bind(this), - timeout: flushInterval, - }) - this.started = false - } - - start(): void { - this.started = true - // dont start the timer until the first event is enqueued - } - - stop(): Promise<any> { - this.started = false - const result = this.sink(this.buffer) - this.buffer = [] - this.timer.stop() - return result - } - - enqueue(event: K): void { - if (!this.started) { - logger.warn('Queue is stopped, not accepting event') - return - } - - // If new event cannot be included into the current batch, flush so it can - // be in its own new batch. - const bufferedEvent: K | undefined = this.buffer[0] - if (bufferedEvent && !this.batchComparator(bufferedEvent, event)) { - this.flush() - } - - // start the timer when the first event is put in - if (this.buffer.length === 0) { - this.timer.refresh() - } - this.buffer.push(event) - - if (this.buffer.length >= this.maxQueueSize) { - this.flush() - } - } - - flush() { - this.sink(this.buffer) - this.buffer = [] - this.timer.stop() - } -} diff --git a/packages/event-processor/src/events.ts b/packages/event-processor/src/events.ts deleted file mode 100644 index a28d86773..000000000 --- a/packages/event-processor/src/events.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export type VisitorAttribute = { - entityId: string - key: string - value: string | number | boolean -} - -export interface BaseEvent { - type: 'impression' | 'conversion' - timestamp: number - uuid: string - - // projectConfig stuff - context: { - accountId: string - projectId: string - clientName: string - clientVersion: string - revision: string - anonymizeIP: boolean - botFiltering?: boolean - } -} - -export interface ImpressionEvent extends BaseEvent { - type: 'impression' - - user: { - id: string - attributes: VisitorAttribute[] - } - - layer: { - id: string | null - } | null - - experiment: { - id: string | null - key: string - } | null - - variation: { - id: string | null - key: string - } | null - - ruleKey: string - flagKey: string - ruleType: string - enabled: boolean -} - -export interface ConversionEvent extends BaseEvent { - type: 'conversion' - - user: { - id: string - attributes: VisitorAttribute[] - } - - event: { - id: string | null - key: string - } - - revenue: number | null - value: number | null - tags: EventTags | undefined -} - -export type EventTags = { - [key: string]: string | number | null -} - -export function areEventContextsEqual(eventA: BaseEvent, eventB: BaseEvent): boolean { - const contextA = eventA.context - const contextB = eventB.context - return ( - contextA.accountId === contextB.accountId && - contextA.projectId === contextB.projectId && - contextA.clientName === contextB.clientName && - contextA.clientVersion === contextB.clientVersion && - contextA.revision === contextB.revision && - contextA.anonymizeIP === contextB.anonymizeIP && - contextA.botFiltering === contextB.botFiltering - ) -} diff --git a/packages/event-processor/src/index.react_native.ts b/packages/event-processor/src/index.react_native.ts deleted file mode 100644 index 2e3ac34e4..000000000 --- a/packages/event-processor/src/index.react_native.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor.react_native' diff --git a/packages/event-processor/src/index.ts b/packages/event-processor/src/index.ts deleted file mode 100644 index d2f916615..000000000 --- a/packages/event-processor/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor' diff --git a/packages/event-processor/src/managed.ts b/packages/event-processor/src/managed.ts deleted file mode 100644 index 686d2aa0f..000000000 --- a/packages/event-processor/src/managed.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export interface Managed { - start(): void - - stop(): Promise<any> -} diff --git a/packages/event-processor/src/pendingEventsDispatcher.ts b/packages/event-processor/src/pendingEventsDispatcher.ts deleted file mode 100644 index f94f9a253..000000000 --- a/packages/event-processor/src/pendingEventsDispatcher.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '@optimizely/js-sdk-logging' -import { EventDispatcher, EventV1Request, EventDispatcherCallback } from './eventDispatcher' -import { PendingEventsStore, LocalStorageStore } from './pendingEventsStore' -import { generateUUID, getTimestamp } from '@optimizely/js-sdk-utils' - -const logger = getLogger('EventProcessor') - -export type DispatcherEntry = { - uuid: string - timestamp: number - request: EventV1Request -} - -export class PendingEventsDispatcher implements EventDispatcher { - protected dispatcher: EventDispatcher - protected store: PendingEventsStore<DispatcherEntry> - - constructor({ - eventDispatcher, - store, - }: { - eventDispatcher: EventDispatcher - store: PendingEventsStore<DispatcherEntry> - }) { - this.dispatcher = eventDispatcher - this.store = store - } - - dispatchEvent(request: EventV1Request, callback: EventDispatcherCallback): void { - this.send( - { - uuid: generateUUID(), - timestamp: getTimestamp(), - request, - }, - callback, - ) - } - - sendPendingEvents(): void { - const pendingEvents = this.store.values() - - logger.debug('Sending %s pending events from previous page', pendingEvents.length) - - pendingEvents.forEach(item => { - try { - this.send(item, () => {}) - } catch (e) {} - }) - } - - protected send(entry: DispatcherEntry, callback: EventDispatcherCallback): void { - this.store.set(entry.uuid, entry) - - this.dispatcher.dispatchEvent(entry.request, response => { - this.store.remove(entry.uuid) - callback(response) - }) - } -} - -export class LocalStoragePendingEventsDispatcher extends PendingEventsDispatcher { - constructor({ eventDispatcher }: { eventDispatcher: EventDispatcher }) { - super({ - eventDispatcher, - store: new LocalStorageStore({ - // TODO make this configurable - maxValues: 100, - key: 'fs_optly_pending_events', - }), - }) - } -} diff --git a/packages/event-processor/src/pendingEventsStore.ts b/packages/event-processor/src/pendingEventsStore.ts deleted file mode 100644 index 60f424c71..000000000 --- a/packages/event-processor/src/pendingEventsStore.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { objectValues } from '@optimizely/js-sdk-utils' -import { getLogger } from '@optimizely/js-sdk-logging'; - -const logger = getLogger('EventProcessor') - -export interface PendingEventsStore<K> { - get(key: string): K | null - - set(key: string, value: K): void - - remove(key: string): void - - values(): K[] - - clear(): void - - replace(newMap: { [key: string]: K }): void -} - -interface StoreEntry { - uuid: string - timestamp: number -} - -export class LocalStorageStore<K extends StoreEntry> implements PendingEventsStore<K> { - protected LS_KEY: string - protected maxValues: number - - constructor({ key, maxValues = 1000 }: { key: string; maxValues?: number }) { - this.LS_KEY = key - this.maxValues = maxValues - } - - get(key: string): K | null { - return this.getMap()[key] || null - } - - set(key: string, value: K): void { - const map = this.getMap() - map[key] = value - this.replace(map) - } - - remove(key: string): void { - const map = this.getMap() - delete map[key] - this.replace(map) - } - - values(): K[] { - return objectValues(this.getMap()) - } - - clear(): void { - this.replace({}) - } - - replace(map: { [key: string]: K }): void { - try { - // This is a temporary fix to support React Native which does not have localStorage. - window.localStorage && localStorage.setItem(this.LS_KEY, JSON.stringify(map)) - this.clean() - } catch (e: any) { - logger.error(e) - } - } - - private clean() { - const map = this.getMap() - const keys = Object.keys(map) - const toRemove = keys.length - this.maxValues - if (toRemove < 1) { - return - } - - const entries = keys.map(key => ({ - key, - value: map[key] - })) - - entries.sort((a, b) => a.value.timestamp - b.value.timestamp) - - for (var i = 0; i < toRemove; i++) { - delete map[entries[i].key] - } - - this.replace(map) - } - - private getMap(): { [key: string]: K } { - try { - // This is a temporary fix to support React Native which does not have localStorage. - const data = window.localStorage && localStorage.getItem(this.LS_KEY); - if (data) { - return (JSON.parse(data) as { [key: string]: K }) || {} - } - } catch (e: any) { - logger.error(e) - } - return {} - } -} diff --git a/packages/event-processor/src/persistentKeyValueCache.ts b/packages/event-processor/src/persistentKeyValueCache.ts deleted file mode 100644 index 9b3ef9fac..000000000 --- a/packages/event-processor/src/persistentKeyValueCache.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys - * and JSON Object as value. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. Object if value found was stored as a JSON Object. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<any | null>; - - /** - * Stores Object in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: any): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/packages/event-processor/src/reactNativeAsyncStorageCache.ts b/packages/event-processor/src/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 54d057cb1..000000000 --- a/packages/event-processor/src/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<any | null> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return null; - } - try { - return JSON.parse(val); - } catch (ex) { - throw ex; - } - }); - } - - set(key: string, val: any): Promise<void> { - try { - return AsyncStorage.setItem(key, JSON.stringify(val)); - } catch (ex) { - return Promise.reject(ex); - } - } - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/packages/event-processor/src/reactNativeEventsStore.ts b/packages/event-processor/src/reactNativeEventsStore.ts deleted file mode 100644 index ff303f01c..000000000 --- a/packages/event-processor/src/reactNativeEventsStore.ts +++ /dev/null @@ -1,81 +0,0 @@ - -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '@optimizely/js-sdk-logging' -import { objectValues } from "@optimizely/js-sdk-utils" - -import { Synchronizer } from './synchronizer' -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache' - -const logger = getLogger('ReactNativeEventsStore') - -/** - * A key value store which stores objects of type T with string keys - */ -export class ReactNativeEventsStore<T> { - private maxSize: number - private storeKey: string - private synchronizer: Synchronizer = new Synchronizer() - private cache: ReactNativeAsyncStorageCache = new ReactNativeAsyncStorageCache() - - constructor(maxSize: number, storeKey: string) { - this.maxSize = maxSize - this.storeKey = storeKey - } - - public async set(key: string, event: T): Promise<string> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - if (Object.keys(eventsMap).length < this.maxSize) { - eventsMap[key] = event - await this.cache.set(this.storeKey, eventsMap) - } else { - logger.warn('React native events store is full. Store key: %s', this.storeKey) - } - this.synchronizer.releaseLock() - return key - } - - public async get(key: string): Promise<T> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - this.synchronizer.releaseLock() - return eventsMap[key] - } - - public async getEventsMap(): Promise<{[key: string]: T}> { - return await this.cache.get(this.storeKey) || {} - } - - public async getEventsList(): Promise<T[]> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - this.synchronizer.releaseLock() - return objectValues(eventsMap) - } - - public async remove(key: string): Promise<void> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - eventsMap[key] && delete eventsMap[key] - await this.cache.set(this.storeKey, eventsMap) - this.synchronizer.releaseLock() - } - - public async clear(): Promise<void> { - await this.cache.remove(this.storeKey) - } -} diff --git a/packages/event-processor/src/requestTracker.ts b/packages/event-processor/src/requestTracker.ts deleted file mode 100644 index e3f774690..000000000 --- a/packages/event-processor/src/requestTracker.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * RequestTracker keeps track of in-flight requests for EventProcessor using - * an internal counter. It exposes methods for adding a new request to be - * tracked, and getting a Promise representing the completion of currently - * tracked requests. - */ -class RequestTracker { - private reqsInFlightCount: number = 0 - private reqsCompleteResolvers: Array<() => void> = [] - - /** - * Track the argument request (represented by a Promise). reqPromise will feed - * into the state of Promises returned by onRequestsComplete. - * @param {Promise<void>} reqPromise - */ - public trackRequest(reqPromise: Promise<void>): void { - this.reqsInFlightCount++ - const onReqComplete = () => { - this.reqsInFlightCount-- - if (this.reqsInFlightCount === 0) { - this.reqsCompleteResolvers.forEach(resolver => resolver()) - this.reqsCompleteResolvers = [] - } - } - reqPromise.then(onReqComplete, onReqComplete) - } - - /** - * Return a Promise that fulfills after all currently-tracked request promises - * are resolved. - * @return {Promise<void>} - */ - public onRequestsComplete(): Promise<void> { - return new Promise(resolve => { - if (this.reqsInFlightCount === 0) { - resolve() - } else { - this.reqsCompleteResolvers.push(resolve) - } - }) - } -} - -export default RequestTracker diff --git a/packages/event-processor/src/synchronizer.ts b/packages/event-processor/src/synchronizer.ts deleted file mode 100644 index 2d5d861f2..000000000 --- a/packages/event-processor/src/synchronizer.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This synchronizer makes sure the operations are atomic using promises. - */ -export class Synchronizer { - private lockPromises: Promise<void>[] = [] - private resolvers: any[] = [] - - // Adds a promise to the existing list and returns the promise so that the code block can wait for its turn - public async getLock(): Promise<void> { - this.lockPromises.push(new Promise(resolve => this.resolvers.push(resolve))) - if (this.lockPromises.length === 1) { - return - } - await this.lockPromises[this.lockPromises.length - 2] - } - - // Resolves first promise in the array so that the code block waiting on the first promise can continue execution - public releaseLock(): void { - if (this.lockPromises.length > 0) { - this.lockPromises.shift() - const resolver = this.resolvers.shift() - resolver() - return - } - } -} diff --git a/packages/event-processor/src/v1/buildEventV1.ts b/packages/event-processor/src/v1/buildEventV1.ts deleted file mode 100644 index 59ab01017..000000000 --- a/packages/event-processor/src/v1/buildEventV1.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { EventTags, ConversionEvent, ImpressionEvent, VisitorAttribute } from '../events' -import { ProcessableEvent } from '../eventProcessor' -import { EventV1Request } from '../eventDispatcher' - -const ACTIVATE_EVENT_KEY = 'campaign_activated' -const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' -const BOT_FILTERING_KEY = '$opt_bot_filtering' - -export type EventV1 = { - account_id: string - project_id: string - revision: string - client_name: string - client_version: string - anonymize_ip: boolean - enrich_decisions: boolean - visitors: Visitor[] -} - -type Visitor = { - snapshots: Visitor.Snapshot[] - visitor_id: string - attributes: Visitor.Attribute[] -} - -namespace Visitor { - type AttributeType = 'custom' - - export type Attribute = { - // attribute id - entity_id: string - // attribute key - key: string - type: AttributeType - value: string | number | boolean - } - - export type Snapshot = { - decisions?: Decision[] - events: SnapshotEvent[] - } - - type Decision = { - campaign_id: string | null - experiment_id: string | null - variation_id: string | null - metadata: Metadata - } - - type Metadata = { - flag_key: string; - rule_key: string; - rule_type: string; - variation_key: string; - enabled: boolean; - } - - export type SnapshotEvent = { - entity_id: string | null - timestamp: number - uuid: string - key: string - revenue?: number - value?: number - tags?: EventTags - } -} - - - -type Attributes = { - [key: string]: string | number | boolean -} - -/** - * Given an array of batchable Decision or ConversionEvent events it returns - * a single EventV1 with proper batching - * - * @param {ProcessableEvent[]} events - * @returns {EventV1} - */ -export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 { - const visitors: Visitor[] = [] - const data = events[0] - - events.forEach(event => { - if (event.type === 'conversion' || event.type === 'impression') { - let visitor = makeVisitor(event) - - if (event.type === 'impression') { - visitor.snapshots.push(makeDecisionSnapshot(event)) - } else if (event.type === 'conversion') { - visitor.snapshots.push(makeConversionSnapshot(event)) - } - - visitors.push(visitor) - } - }) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors, - } -} - -function makeConversionSnapshot(conversion: ConversionEvent): Visitor.Snapshot { - let tags: EventTags = { - ...conversion.tags, - } - - delete tags['revenue'] - delete tags['value'] - - const event: Visitor.SnapshotEvent = { - entity_id: conversion.event.id, - key: conversion.event.key, - timestamp: conversion.timestamp, - uuid: conversion.uuid, - } - - if (conversion.tags) { - event.tags = conversion.tags - } - - if (conversion.value != null) { - event.value = conversion.value - } - - if (conversion.revenue != null) { - event.revenue = conversion.revenue - } - - return { - events: [event], - } -} - -function makeDecisionSnapshot(event: ImpressionEvent): Visitor.Snapshot { - const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event - let layerId = layer ? layer.id : null - let experimentId = experiment?.id ?? '' - let variationId = variation?.id ?? '' - let variationKey = variation ? variation.key : '' - - return { - decisions: [ - { - campaign_id: layerId, - experiment_id: experimentId, - variation_id: variationId, - metadata: { - flag_key: flagKey, - rule_key: ruleKey, - rule_type: ruleType, - variation_key: variationKey, - enabled: enabled, - }, - }, - ], - events: [ - { - entity_id: layerId, - timestamp: event.timestamp, - key: ACTIVATE_EVENT_KEY, - uuid: event.uuid, - }, - ], - } -} - -function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { - const visitor: Visitor = { - snapshots: [], - visitor_id: data.user.id, - attributes: [], - } - - data.user.attributes.forEach(attr => { - visitor.attributes.push({ - entity_id: attr.entityId, - key: attr.key, - type: 'custom' as 'custom', // tell the compiler this is always string "custom" - value: attr.value, - }) - }) - - if (typeof data.context.botFiltering === 'boolean') { - visitor.attributes.push({ - entity_id: BOT_FILTERING_KEY, - key: BOT_FILTERING_KEY, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: data.context.botFiltering, - }) - } - return visitor -} - -/** - * Event for usage with v1 logtier - * - * @export - * @interface EventBuilderV1 - */ - -export function buildImpressionEventV1(data: ImpressionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeDecisionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function buildConversionEventV1(data: ConversionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeConversionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function formatEvents(events: ProcessableEvent[]): EventV1Request { - return { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(events), - } -} diff --git a/packages/event-processor/src/v1/v1EventProcessor.react_native.ts b/packages/event-processor/src/v1/v1EventProcessor.react_native.ts deleted file mode 100644 index cf2274674..000000000 --- a/packages/event-processor/src/v1/v1EventProcessor.react_native.ts +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - generateUUID, - NotificationCenter, - objectEntries, -} from '@optimizely/js-sdk-utils' -import { - NetInfoState, - addEventListener as addConnectionListener, -} from "@react-native-community/netinfo" -import { getLogger } from '@optimizely/js-sdk-logging' - -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from "../eventProcessor" -import { ReactNativeEventsStore } from '../reactNativeEventsStore' -import { Synchronizer } from '../synchronizer' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' -import { - EventV1Request, - EventDispatcher, - EventDispatcherResponse, -} from '../eventDispatcher' - -const logger = getLogger('ReactNativeEventProcessor') - -const DEFAULT_MAX_QUEUE_SIZE = 10000 -const PENDING_EVENTS_STORE_KEY = 'fs_optly_pending_events' -const EVENT_BUFFER_STORE_KEY = 'fs_optly_event_buffer' - -/** - * React Native Events Processor with Caching support for events when app is offline. - */ -export class LogTierV1EventProcessor implements EventProcessor { - private dispatcher: EventDispatcher - // expose for testing - public queue: EventQueue<ProcessableEvent> - private notificationCenter?: NotificationCenter - private requestTracker: RequestTracker - - private unsubscribeNetInfo: Function | null = null - private isInternetReachable: boolean = true - private pendingEventsPromise: Promise<void> | null = null - private synchronizer: Synchronizer = new Synchronizer() - - // If a pending event fails to dispatch, this indicates skipping further events to preserve sequence in the next retry. - private shouldSkipDispatchToPreserveSequence: boolean = false - - /** - * This Stores Formatted events before dispatching. The events are removed after they are successfully dispatched. - * Stored events are retried on every new event dispatch, when connection becomes available again or when SDK initializes the next time. - */ - private pendingEventsStore: ReactNativeEventsStore<EventV1Request> - - /** - * This stores individual events generated from the SDK till they are part of the pending buffer. - * The store is cleared right before the event is formatted to be dispatched. - * This is to make sure that individual events are not lost when app closes before the buffer was flushed. - */ - private eventBufferStore: ReactNativeEventsStore<ProcessableEvent> - - constructor({ - dispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - maxQueueSize = DEFAULT_MAX_QUEUE_SIZE, - notificationCenter, - }: { - dispatcher: EventDispatcher - flushInterval?: number - batchSize?: number - maxQueueSize?: number - notificationCenter?: NotificationCenter - }) { - this.dispatcher = dispatcher - this.notificationCenter = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) - this.pendingEventsStore = new ReactNativeEventsStore(maxQueueSize, PENDING_EVENTS_STORE_KEY) - this.eventBufferStore = new ReactNativeEventsStore(maxQueueSize, EVENT_BUFFER_STORE_KEY) - } - - private async connectionListener(state: NetInfoState) { - if (this.isInternetReachable && !state.isInternetReachable) { - this.isInternetReachable = false - logger.debug('Internet connection lost') - return - } - if (!this.isInternetReachable && state.isInternetReachable) { - this.isInternetReachable = true - logger.debug('Internet connection is restored, attempting to dispatch pending events') - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - } - } - - private isSuccessResponse(status: number): boolean { - return status >= 200 && status < 400 - } - - private async drainQueue(buffer: ProcessableEvent[]): Promise<void> { - if (buffer.length === 0) { - return - } - - await this.synchronizer.getLock() - - // Retry pending failed events while draining queue - await this.processPendingEvents() - - logger.debug('draining queue with %s events', buffer.length) - - const eventCacheKey = generateUUID() - const formattedEvent = formatEvents(buffer) - - // Store formatted event before dispatching to be retried later in case of failure. - await this.pendingEventsStore.set(eventCacheKey, formattedEvent) - - // Clear buffer because the buffer has become a formatted event and is already stored in pending cache. - for (const {uuid} of buffer) { - await this.eventBufferStore.remove(uuid) - } - - if (!this.shouldSkipDispatchToPreserveSequence) { - await this.dispatchEvent(eventCacheKey, formattedEvent) - } - - // Resetting skip flag because current sequence of events have all been processed - this.shouldSkipDispatchToPreserveSequence = false - - this.synchronizer.releaseLock() - } - - private async processPendingEvents(): Promise<void> { - logger.debug('Processing pending events from offline storage') - if (!this.pendingEventsPromise) { - // Only process events if existing promise is not in progress - this.pendingEventsPromise = this.getPendingEventsPromise() - } else { - logger.debug('Already processing pending events, returning the existing promise') - } - await this.pendingEventsPromise - this.pendingEventsPromise = null - } - - private async getPendingEventsPromise(): Promise<void> { - const formattedEvents: {[key: string]: any} = await this.pendingEventsStore.getEventsMap() - const eventEntries = objectEntries(formattedEvents) - logger.debug('Processing %s pending events', eventEntries.length) - // Using for loop to be able to wait for previous dispatch to finish before moving on to the new one - for (const [eventKey, event] of eventEntries) { - // If one event dispatch failed, skip subsequent events to preserve sequence - if (this.shouldSkipDispatchToPreserveSequence) { - return - } - await this.dispatchEvent(eventKey, event) - } - } - - private async dispatchEvent(eventCacheKey: string, event: EventV1Request): Promise<void> { - const requestPromise = new Promise<void>((resolve) => { - this.dispatcher.dispatchEvent(event, async ({ statusCode }: EventDispatcherResponse) => { - if (this.isSuccessResponse(statusCode)) { - await this.pendingEventsStore.remove(eventCacheKey) - } else { - this.shouldSkipDispatchToPreserveSequence = true - logger.warn('Failed to dispatch event, Response status Code: %s', statusCode) - } - resolve() - }) - sendEventNotification(this.notificationCenter, event) - }) - // Tracking all the requests to dispatch to make sure request is completed before fulfilling the `stop` promise - this.requestTracker.trackRequest(requestPromise) - return requestPromise - } - - public async start(): Promise<void> { - this.queue.start() - this.unsubscribeNetInfo = addConnectionListener(this.connectionListener.bind(this)) - - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - - // Process individual events pending from the buffer. - const events: ProcessableEvent[] = await this.eventBufferStore.getEventsList() - await this.eventBufferStore.clear() - events.forEach(this.process.bind(this)) - } - - public process(event: ProcessableEvent): void { - // Adding events to buffer store. If app closes before dispatch, we can reprocess next time the app initializes - this.eventBufferStore.set(event.uuid, event).then(() => { - this.queue.enqueue(event) - }) - } - - public async stop(): Promise<void> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.unsubscribeNetInfo && this.unsubscribeNetInfo() - await this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e: any) { - logger.error('Error stopping EventProcessor: "%s"', e?.message, e) - } - } -} diff --git a/packages/event-processor/src/v1/v1EventProcessor.ts b/packages/event-processor/src/v1/v1EventProcessor.ts deleted file mode 100644 index 1dcbf0768..000000000 --- a/packages/event-processor/src/v1/v1EventProcessor.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '@optimizely/js-sdk-logging' -import { NotificationCenter } from '@optimizely/js-sdk-utils' - -import { EventDispatcher } from '../eventDispatcher' -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from '../eventProcessor' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' - -const logger = getLogger('LogTierV1EventProcessor') - -export class LogTierV1EventProcessor implements EventProcessor { - private dispatcher: EventDispatcher - private queue: EventQueue<ProcessableEvent> - private notificationCenter?: NotificationCenter - private requestTracker: RequestTracker - - constructor({ - dispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - notificationCenter, - }: { - dispatcher: EventDispatcher - flushInterval?: number - batchSize?: number - notificationCenter?: NotificationCenter - }) { - this.dispatcher = dispatcher - this.notificationCenter = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) - } - - drainQueue(buffer: ProcessableEvent[]): Promise<void> { - const reqPromise = new Promise<void>(resolve => { - logger.debug('draining queue with %s events', buffer.length) - - if (buffer.length === 0) { - resolve() - return - } - - const formattedEvent = formatEvents(buffer) - this.dispatcher.dispatchEvent(formattedEvent, () => { - resolve() - }) - sendEventNotification(this.notificationCenter, formattedEvent) - }) - this.requestTracker.trackRequest(reqPromise) - return reqPromise - } - - process(event: ProcessableEvent): void { - this.queue.enqueue(event) - } - - stop(): Promise<any> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e: any) { - logger.error('Error stopping EventProcessor: "%s"', e.message, e) - } - return Promise.resolve() - } - - async start(): Promise<void> { - this.queue.start() - } -} diff --git a/packages/event-processor/tsconfig.json b/packages/event-processor/tsconfig.json deleted file mode 100644 index 8ca97f079..000000000 --- a/packages/event-processor/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib", - "noImplicitAny": true, - "strictNullChecks": true, - }, - "include": [ - "./src" - ], - "exclude": [ - "./lib", - "./src/**/*.spec.ts" - ] -} \ No newline at end of file diff --git a/packages/optimizely-sdk/README.md b/packages/optimizely-sdk/README.md index 8eae7d1c9..e614af651 100644 --- a/packages/optimizely-sdk/README.md +++ b/packages/optimizely-sdk/README.md @@ -118,7 +118,6 @@ This repository houses the main Javascript SDK and its supporting packages. | Package | Version | Docs | Description | | - | - | - | - | | [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | (Consolidated*) Event Batching support for Optimizely SDK | [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK | [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages From 9d63b60f68ce7957fb0948ed4e7bf9104de30485 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 14 Aug 2023 22:34:50 +0600 Subject: [PATCH 032/200] [FSSDK-8219] removed logging subpackage (#848) --- .github/workflows/javascript.yml | 2 +- README.md | 1 - packages/logging/.gitignore | 2 - packages/logging/CHANGELOG.md | 27 - packages/logging/LICENSE | 202 - packages/logging/README.md | 156 - packages/logging/__tests__/logger.spec.ts | 386 -- packages/logging/jest.config.js | 17 - packages/logging/logging_architecture.png | Bin 53291 -> 0 bytes packages/logging/package-lock.json | 5597 --------------------- packages/logging/package.json | 48 - packages/logging/src/errorHandler.ts | 67 - packages/logging/src/index.ts | 18 - packages/logging/src/logger.ts | 328 -- packages/logging/src/models.ts | 42 - packages/logging/tsconfig.json | 13 - packages/optimizely-sdk/README.md | 1 - 17 files changed, 1 insertion(+), 6906 deletions(-) delete mode 100644 packages/logging/.gitignore delete mode 100644 packages/logging/CHANGELOG.md delete mode 100644 packages/logging/LICENSE delete mode 100644 packages/logging/README.md delete mode 100644 packages/logging/__tests__/logger.spec.ts delete mode 100644 packages/logging/jest.config.js delete mode 100644 packages/logging/logging_architecture.png delete mode 100644 packages/logging/package-lock.json delete mode 100644 packages/logging/package.json delete mode 100644 packages/logging/src/errorHandler.ts delete mode 100644 packages/logging/src/index.ts delete mode 100644 packages/logging/src/logger.ts delete mode 100644 packages/logging/src/models.ts delete mode 100644 packages/logging/tsconfig.json diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 811e78373..22059ccf2 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -109,7 +109,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - package: [ 'packages/utils', 'packages/logging'] + package: [ 'packages/utils' ] steps: - uses: actions/checkout@v3 - name: Move to package ${{ matrix.package }} diff --git a/README.md b/README.md index 9b93f4bb7..1865fd289 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,6 @@ This repository is a monorepo. It houses the main Javascript SDK and its support | Package | Version | Docs | Description | | - | - | - | - | | [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK | [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages > \* Consolidated packages have been copied over and included as modules within the main `@optimizely/optimizely-sdk` package to avoid requiring maintaining and utilizing multiple de-coupled dependencies. (Related PRs [#749](https://github.com/optimizely/javascript-sdk/pull/749), [#755](https://github.com/optimizely/javascript-sdk/pull/755/files), [#761](https://github.com/optimizely/javascript-sdk/pull/761), [#781](https://github.com/optimizely/javascript-sdk/pull/781)) diff --git a/packages/logging/.gitignore b/packages/logging/.gitignore deleted file mode 100644 index d4a125901..000000000 --- a/packages/logging/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lib/ -doc/ diff --git a/packages/logging/CHANGELOG.md b/packages/logging/CHANGELOG.md deleted file mode 100644 index 180abfe5f..000000000 --- a/packages/logging/CHANGELOG.md +++ /dev/null @@ -1,27 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.3.1] - October 13, 2021 - -### Bug Fixes -- Downgrade typescript version to `3.8.x` due to stubbing issues. - -## [0.3.0] - October 8, 2021 - -### New Features -- Upgraded `@optimizely/js-sdk-utils` package to version `0.4.0` - -## [0.2.0] - October 6, 2021 - -### New Features -- Added optional args to `logger.log` to format string only when log level applies ([#706](https://github.com/optimizely/javascript-sdk/pull/706)). - -## [0.1.0] - March 1, 2019 - -Initial release diff --git a/packages/logging/LICENSE b/packages/logging/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/logging/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/logging/README.md b/packages/logging/README.md deleted file mode 100644 index efa69a82d..000000000 --- a/packages/logging/README.md +++ /dev/null @@ -1,156 +0,0 @@ -# Javascript SDK Logging - -Provides a centralized LogManager and errorHandler for Javascript SDK packages. - -## Installation - -```sh -npm install @optimizely/js-sdk-logging -``` - -## Architecture - -![Logging Architecture](./logging_architecture.png) - - - - **LogHandler** - the component that determines where to write logs. Common log handlers - are `ConsoleLogHandler` or `NoopLogHandler` - - **LogManager** - returns Logger facade instances via LogManager.getLogger(name) - - **LoggerFacade** the internal logging interface available to other packages via `LogManager.getLogger(name)` - - -## Usage - - -#### Using the logger - -```typescript -import { getLogger } from '@optimizely/js-sdk-logging' - -const logger = getLogger('myModule') -logger.log('warn', 'this is a warning') - -logger.debug('string interpolation is easy and %s', 'lazily evaluated') - -logger.info('info logging') -logger.warn('this is a warning') -logger.error('this is an error') - -// `info` `warn` `debug` and `error` all support passing an Error as the last argument -// this will call the registered errorHandler -logger.error('an error occurred: %s', ex.message) - -// also Error passes to errorHandler.handleError(ex) -logger.error('an error occurred: %s', ex.message, ex) - -// if no message is passed will log `ex.message` -logger.error(ex) -``` - -#### Setting the log level - -```typescript -import { LogLevel, setLogLevel } from '@optimizely/js-sdk-logging' - -// can use enum -setLogLevel(LogLevel.ERROR) - -// can also use a string (lowercase or uppercase) -setLogLevel('debug') -setLogLevel('info') -setLogLevel('warn') -setLogLevel('error') -``` - - -#### Setting a LogHandler - -```typescript -import { setLogHandler, ConsoleLogHandler } from '@optimizely/js-sdk-logging' - -const handler = new ConsoleLogHandler({ - logLevel: 'error', - prefix: '[My custom prefix]', // defaults to "[OPTIMIZELY]" -}) - -setLogHandler(handler) -``` - -#### Implementing a custom LogHandler - -Perhaps you want to integrate Optimizely with your own logging system or use an existing library. - -A valid `LogHandler` is anything that implements this interface - -```typescript -interface LogHandler { - log(level: LogLevel, message: string): void -} -``` - -**Example: integrating with Winston** - -```js -import winston from 'winston' -import { setLogHandler, LogLevel } from '@optimizely/js-sdk-logging' - -const winstonLogger = winston.createLogger({ - level: 'info', - format: winston.format.json(), - defaultMeta: { service: 'optimizely' }, - transports: [ - new winston.transports.File({ filename: 'combined.log' }), - ], -}) - -/** - * Convert from optimizely log levels to winston - */ -function convertLogLevels(level) { - switch(level) { - case LogLevel.DEBUG: - return 'debug' - case LogLevel.INFO: - return 'info' - case LogLevel.WARNING: - return 'warning' - case LogLevel.ERROR: - return 'error' - default: - return 'silly' - } -} - -setLogHandler({ - log(level, message) { - winstoLogger.log({ - level: convertLogLevels(level), - message, - }) - } -}) -``` - -### API Interfaces - -```typescript -interface LoggerFacade { - log(level: LogLevel | string, message: string): void - - info(message: string | Error, ...splat: any[]): void - - debug(message: string | Error, ...splat: any[]): void - - warn(message: string | Error, ...splat: any[]): void - - error(message: string | Error, ...splat: any[]): void -} - -interface LogManager { - getLogger(name?: string): LoggerFacade -} - -interface LogHandler { - log(level: LogLevel, message: string): void -} -``` \ No newline at end of file diff --git a/packages/logging/__tests__/logger.spec.ts b/packages/logging/__tests__/logger.spec.ts deleted file mode 100644 index 10529d94a..000000000 --- a/packages/logging/__tests__/logger.spec.ts +++ /dev/null @@ -1,386 +0,0 @@ -/// <reference types="jest" /> -import { - LogLevel, - LogHandler, - LoggerFacade, -} from '../src/models' - -import { - setLogHandler, - setLogLevel, - getLogger, - ConsoleLogHandler, - resetLogger, - getLogLevel, -} from '../src/logger' - -import { resetErrorHandler } from '../src/errorHandler' -import { ErrorHandler, setErrorHandler } from '../src/errorHandler' - -describe('logger', () => { - afterEach(() => { - resetLogger() - resetErrorHandler() - }) - - describe('OptimizelyLogger', () => { - let stubLogger: LogHandler - let logger: LoggerFacade - let stubErrorHandler: ErrorHandler - - beforeEach(() => { - stubLogger = { - log: jest.fn(), - } - stubErrorHandler = { - handleError: jest.fn(), - } - setLogLevel(LogLevel.DEBUG) - setLogHandler(stubLogger) - setErrorHandler(stubErrorHandler) - logger = getLogger() - }) - - describe('setLogLevel', () => { - it('should coerce "debug"', () => { - setLogLevel('debug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "deBug"', () => { - setLogLevel('deBug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "INFO"', () => { - setLogLevel('INFO') - expect(getLogLevel()).toBe(LogLevel.INFO) - }) - - it('should coerce "WARN"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "warning"', () => { - setLogLevel('warning') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "ERROR"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should default to error if invalid', () => { - setLogLevel('invalid') - expect(getLogLevel()).toBe(LogLevel.ERROR) - }) - }) - - describe('getLogger(name)', () => { - it('should prepend the name in the log messages', () => { - const myLogger = getLogger('doit') - myLogger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'doit: test') - }) - }) - - describe('logger.log(level, msg)', () => { - it('should work with a string logLevel', () => { - setLogLevel(LogLevel.INFO) - logger.log('info', 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is equal to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.INFO, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.WARNING, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should not call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.DEBUG, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(0) - }) - - it('should not throw if loggerBackend is not supplied', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.ERROR, 'test') - }) - }) - - describe('logger.info', () => { - it('should handle info(message)', () => { - logger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - it('should handle info(message, ...splat)', () => { - logger.info('test: %s %s', 'hey', 'jude') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey jude') - }) - - it('should handle info(message, ...splat, error)', () => { - const error = new Error('hey') - logger.info('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.info(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.debug', () => { - it('should handle debug(message)', () => { - logger.debug('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test') - }) - - it('should handle debug(message, ...splat)', () => { - logger.debug('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - }) - - it('should handle debug(message, ...splat, error)', () => { - const error = new Error('hey') - logger.debug('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle debug(error)', () => { - const error = new Error('hey') - logger.debug(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.warn', () => { - it('should handle warn(message)', () => { - logger.warn('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should handle warn(message, ...splat)', () => { - logger.warn('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - }) - - it('should handle warn(message, ...splat, error)', () => { - const error = new Error('hey') - logger.warn('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.warn(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.error', () => { - it('should handle error(message)', () => { - logger.error('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test') - }) - - it('should handle error(message, ...splat)', () => { - logger.error('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - }) - - it('should handle error(message, ...splat, error)', () => { - const error = new Error('hey') - logger.error('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle error(error)', () => { - const error = new Error('hey') - logger.error(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should work with an insufficient amount of splat args error(msg, ...splat, message)', () => { - const error = new Error('hey') - logger.error('hey %s', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey undefined') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('using ConsoleLoggerHandler', () => { - beforeEach(() => { - jest.spyOn(console, 'info').mockImplementation(() => {}) - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - it('should work with BasicLogger', () => { - const logger = new ConsoleLogHandler() - const TIME = '12:00' - setLogHandler(logger) - setLogLevel(LogLevel.INFO) - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'hey') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 hey') - }) - - it('should set logLevel to ERROR when setLogLevel is called with invalid value', () => { - const logger = new ConsoleLogHandler() - logger.setLogLevel('invalid' as any) - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - - it('should set logLevel to ERROR when setLogLevel is called with no value', () => { - const logger = new ConsoleLogHandler() - // @ts-ignore - logger.setLogLevel() - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - }) - }) - - describe('ConsoleLogger', function() { - beforeEach(() => { - jest.spyOn(console, 'info') - jest.spyOn(console, 'log') - jest.spyOn(console, 'warn') - jest.spyOn(console, 'error') - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - it('should log to console.info for LogLevel.INFO', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'test') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 test') - }) - - it('should log to console.log for LogLevel.DEBUG', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(1) - expect(console.log).toBeCalledWith('[OPTIMIZELY] - DEBUG 12:00 debug') - }) - - it('should log to console.warn for LogLevel.WARNING', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.WARNING, 'warning') - - expect(console.warn).toBeCalledTimes(1) - expect(console.warn).toBeCalledWith('[OPTIMIZELY] - WARN 12:00 warning') - }) - - it('should log to console.error for LogLevel.ERROR', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.ERROR, 'error') - - expect(console.error).toBeCalledTimes(1) - expect(console.error).toBeCalledWith('[OPTIMIZELY] - ERROR 12:00 error') - }) - - it('should not log if the configured logLevel is higher', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.INFO, - }) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(0) - }) - }) -}) diff --git a/packages/logging/jest.config.js b/packages/logging/jest.config.js deleted file mode 100644 index 391afe8eb..000000000 --- a/packages/logging/jest.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - // "roots": [ - // "./src" - // ], - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], -} \ No newline at end of file diff --git a/packages/logging/logging_architecture.png b/packages/logging/logging_architecture.png deleted file mode 100644 index 7f01f74b59548a4b9c328dece796709deb1daa6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53291 zcmb@sWmH^Cv^9z)xLc6m1eZVv+PD+kC1`M`ad)>QxVr|2;1b*$4estVE)BfSIVbmi zW4vE)ypJEv?$uSbcdfl@tvP2^gpz_3Itnog3=9mqjI_843=Hfl3=CW@(rc*Y84e^1 z1A{VcDJG^QBPK?n<YaGVX=4flBOQ^LjHrftKrqmmdKEu`Bl;D`^ZTlZEE0=<pmR0$ zxA<TjQ542Js@f(vYY_>Ak;uw$`X&P5-+|%Ax(H0aUuml)xYYQ+O62aMKJIW^_1b#^ z-H$_d$DdB0PBYHZfrv1*1iH-Q{_3!UctbSGzn-sxdujM8$%SDM!(lMlZDQ3%!=up< z!;7l?=Jr4Ll1#FsM`*n~Lpo(+M!q}2_)}pkY{>GaB4rErbH{2?!NY{Gv+FbBDmq|x zt0C742Zy~&5gOW$;F(EaC{lRn6@QQY69z`nxQ^BvMtVFvGplEV+EMyF(o$5DD0X)U z(xU@{4Quxa5@>vMG;Q4|m__9oC+wv$g&{J<=A*$c)rL<f<P-^!obw|upgC}7Vk%|! zxKSwDH(?wcu0AD2PmPp735H9mBJIJ<n=ns=i}-oVUFlSr=}}X}V1`|@QPk{RH6tuJ zY^napRI{uP?4D_~+RBl7r8w_Q(iXm@wq0Clg?#QZVc6V5G2SfZm3qT%pH_G<X~Ze` zDdaO%oXU@EI+UC2A(Da;fa*iCX9#njUMj^qab<olvnK&;lXpcQL~1^=vQaqn(`jt5 zCEYVQMQ~E#Ket62hI~%Ltw>=K`?x*2EZxWd$s|ltfioNyjslUY>y-cnECU7V7Z{?P zAQ%b+6$;7Id6)$Sj#Z8YamP_+uv0`lTl^%^VCmMRHdXA2O-p`z5F!k&xw}<VTfTgj ze7LJif6@wyztnr<ZP5IDKTL+_YZz1KelrNcf_^#-&vf5cIj16<@8;-1-b89J_!jU! zhG@+G=ne>#U8r{f!dNI&@v=C-jJre*hr}7RQ7*cOl@MgR-c_Tk1p*Aod5~iLO>&Sk zUhn;)bwGCx;9sZQe+9pe|K@eRAzB~>sdy-T{KqdasiME*skbrig#(m$@Lti14q=hw zh&a(>>YyeHn<~-P6PAVehAN2c<uWO8JCh#<z0Pf%Fn{yvyCLHb#PnaT)%>IWJ?n4W zu}8z|HuRdXR$(-|X3ve9umv#<g3h|y&zam2UMb1a6AxpV202N+SH>=(QDc-1)Cpmf zOw2D+zAhn9Lp&rXBjgHx2=32gu;5#KeG-BpGLXl+Z*#@y%+P}4gS8TFC$%Pen;SN{ z#{MOmwV!w^!pI1R-FsB!6Lt|P5C(_?#0DaA`V;jT*L?WF`x~7kP`<awNYhZRrnMS< zUc-j_Cpmv$&qlrDZytL+gxHwx8<7tD4q=Xa4&09LO>yqDZLq7@<iU#FQRlDjoFJ4C zZHPPs2zeutXefJ5BaYP+yb~M~{1i;kjouAo6kQ_ufzA`xB;2x>s~5!>0Hy+CgEPN< z`}Xx)*pxSIu1beW$dq#7h^&unw)7zl6VCVgcp5reirIJ_nOm8&!7sH$3vn6ZNng3+ zt%nE)6$i-^8HRrjGn-akSnV+GNbaN$SE(a07BQkF_Axdy?l2Ch+m)b|;;4<OrIy*u zs+Xyk6|0?<#+E40kj_xeaLmfiVw8SX_R5V_<yOU1+bbh1J5%k<SydEKj%N6T5>}g3 zo9*<KL%2_6td!Y`xFOgxcv0r<Z>2eh-{I3*MaqSalKIJ@d8w0H^=ONdi|-ddFPh5( z<U`*n0^7~ncjd=0b?WLN_3k=-I_NrX^@7&Z4e$-q4cyk;mO{Tpj<gO&=d{b}bocVc zCzubJtF$w;`|4uHJabTwbPgmBDkoNoy*|5%D;ltADgI~_I}2Qm7nEO96q5HVf7X5y zfsYJ`3z#r0|HT`W?2OJAlqFJ?(>NW5?t(6cwiD`w&W}D2JsM*igAz>~O-6znvl62k z(@5M%gqi*(Wt3}(vy{`rP?p_=8$ZR4`-J_;(9Dj&{^$6Qq2rL|B;FB$xwdi0*H;VO zb5u)ti<>`be^0G&tT-i`Cga=e7}Hjl)wUR+>$d8kR=8=qf%->pQdrVH10J)`heD0M zo2Z&ko6FUqH?maQ*813*8z+BhT&V0hO>d9#34QW?48u6Y$i;Yt!GdA`wt|#5Rz0>c zb5KD<p*%A!bCpMz|1!gk|F%g<Z@1+K&=Pomc(}{xSmE~8nbT3ub?LwrlzB*YpnvGQ z0Kc_yR<pQOKR~CbshGO=9wgLm(>B$P;nALAA{fSP{*vKqPi_D6R{u<&yhX7V6erl; z5!zw1X0=xA+wZH_3GWLcN{ar_Z`5z?=j9jiBJ+azNpV-!%o>J~f+vS3hf@i_{;`_l zTqa%_OQ?j^f@hxAUBbZ{=^YL87vLZui7cw8%oIJWC8QF~7Lz(eB1}CLfZm9b9Nah> zvKKnO!f`t(Fzu1&A&*TFh+jjy=~&?L#dVwQvgVR{Pjk$VU<WIaa28uWLLbM6-t$Ah zkeSJ7k5-hHhmCHA>~eS0ViP797HkS`M|MSK!mXq|q(Q+=|L!2;D1-P-k^q5q{->c~ z?nrlDh#O%lo6gVuVdKm`q9_ovvUaz=lK|0o(rj<~dsP0w&tqgVMuo?ph<4d|mw2n1 ze3vVhMY6c7qHhIe`5Tz8U#+Egh6vH0<0qho27fXtJeSvzUd*b;JitESI@lrt6QQTx zjc$*sQn@I%lmO&}thF^7=XLaGPRDLWSCatD$lpohMW-aD%!u$UlPX78w%3LpoxGjO zoCH~8bUnRO7da+tk47qzstle5xan_;@%QmF@NGRpJ*+PtG&nR)KfkJw)t>#i=<c#{ z7ywy^1X+h$Pt8Ge3=Nbt&V6kmi%FvG-H`6*v%t=BCKc9icq_Vl4R)a0{j-oYw@Npi zPLrobV>|FH&f~X>Z)}AbQ{_TD5G8QWUFZyYCdp^+)79V=nnh=~y0hINQ!CSE(<j|s zJIiI+HJ!HWr=PS-l?nXGXA26;tZS>f*!q)gJzoZj-Wo8zN^~oOloPuip6Yd8_OPB- zrnf%1&Yevzsn+gY#&wf>-Ml`t{qeD!buDZqx$bP?x#{VxQ1gqLm++qOP(T$|UfODq zGUAf;ij_6y0okG!;@r^DYfLtQWfz)tX<)nj)9Py-u_0v#HQ%cp(2&p?fWY;~Nz)_v z0k??GHLD<l*)EtknHz%}In6U2H@#IcB+JSgY{h#Yb;8C~jIOAeImuH8W(M1AEP#Q{ zdy+bR6=Hk$dyh*=^?<f`hYi<9$jx|^5h0raN{e%D(4^yO?d{IAra)Cj%b{<>LE2G6 z{}Jn);`8Cf(U8L2ct439cUr4Ji%!d)b9VcI*CnLFUpLvV&d#8f^GxA(Wxf?~OR#!$ zvT@Val<oL37L<W#f_X`9M20V<;iLB~ek*frpVY~9)pC<{fV$uM{NdrC$5-K{e66t4 zkBHt|(pTeodEMg#b%3xKG3a%xKC3(Lxh%xWreq(pLCCe^r@POlVy93QCLJ1P+t)TC zKivm~$IoM4d$+dKU#S%(Ii&^!@%(0<-Os;27RQ3X+1YU{Pl1mr;Moi7k;ZEKxPH!o zfj%2#uUdRzN_YkNSL?RuFx%n<Zu@ZlO@?IyS$H3=4cKMYg}`d$f()84RuGu*F+>W| z17%n09y}u)r%a3Q#t@j}?SeNi2_I&M92p6a4?eyp9~f9Wfc3YAT`&LQ?QJu6cQ^Mm zXnY1gkp`GuQbdY^fD=-!m1}@@Y6aUtev?8lA&W$ChoONbP`sOIsix(uCI5-f#NL+G z=!?CvDXY7!0~G$kzzDeWK`(7hosB5mZEfs;eC`0Me_HTCum70Ys3`tv;%p6|(vnxA z5VLnOrQl-yz{*Y~h(bX@A>j1Ij88>e^1trTZvZL_XJ-dKHa0gmH&!=JR(mINw)ece zylm_oY#bac&=xE}4?AZgcNRM!^}mDs9Y@?0XyRn);B0AcNAYJ|BV&6PX8;w|pBw$p z-@ngk>TdbJcd`TiH!bJ{+5Y^(_MVlU?SICGb`|(z<x{eBH?`3cx3o32148d1_<@^8 z;Gg#YuV4Q6j{iqb?f>ohk(=}X?D>EE@?TE@wm%d2KPL3AxBjt0-6e=3!1h0`7ev8) zB_0g}BMc)WE~4fRdz^`!p)gA_K#nH+=279xTCHhmf@sn=mZ`jWBtKmV739)_U^RL< zanX>H;K3xHD4g0e{-ER07f6<)zT2wD*~R#>TWacFDyYqk_whEvE30k=s4C7xiQ4S@ z#Ft(qn>gG}@O9}1OG`}+PeM}iHJ4uTLHynQIKf6TJ8QS%oh6*gizQBDtjx~6v<H)n z(>sJ<e=ICGQDG#6G=KV4VO)ZjkpB#Ckis{)y5dzSDgJE}@Qxyqdm^~c@2}xC+$n?p z2(3;iR^}onkK@jaWs|~5GY&%#okEX`6==PIsra9ZL}?-cN8(CVuh&(dd0qZoREiI; zM@&0FySbkXm;Si`(yF{x%m_W2$@*ojsgU{Sg6S)V`{oC|;u|#mR;R-cdSN$ISB&rL z1{YGT-RA7|=$PYwYSWx(KiQaLKCd=8_LvxiZTTeJdEhp?ut4l;Cd^yfr0=K2jbkq$ z{kyqU&K{F<W>On_>CxwxW3=73CwD9IU#VVgtjX3l3+gPmEPdKcX3vAV=wMHS99Obe zXinuBvbAHY3mp-BwkYq5?RvJCXYLOZWQ!@SQuK-F$|18zb91kC0;Mz95!<(05jXxp z@q3fYCq_`17qG2yelYc4&mzH68j1?zRUyA=X8F(E6j%u2;Maj=cMSg-x`O@b^>Nvq zbdjK~p$-?OfO;PtK^?66pTQp)R=Mq+;r7q}#)MJ~tUeN?vd!YZH^GIAb*=37YI78~ zjVZh-y;%OupRf*KVsf;?^yOLcklEZzZ#s_}kr-Z+e|G_qeZsi~pC98{XvX!F&agSc z@>LjYb^;Tp`mC}U6$f?h5Ap}Pj>OZC@{=9gSf`8RN!Z*r>8}tfKerNeO5f4@sbFk| zaggfuF(3+F5Q6K=xeZx&C`g??_&SEGVzjkQ-dDxx_n&@#mjj{1aRtla@M}`$D(F~L z)8PmrDDkW;W1};Ccfu09Kl<=a1ta5|?BdNbGa?24IKSTzsr?k|$Tz-!;|z%tF3-J( zS&W7c8l+I?#MdK-Z^|#;GnL}P{jmhFQ$yo<Vm^`d?qlq(cL_AhYxT!vKaIz}c}( z+iG0I)v8)Ap(%1NL&H7tW3AI@<8bohNE!t=&*EgB8t(0?=?lUE%;v8aNb|NC>-JCI zEO1z{V<fs{c>UqIufwJv`bq(GV9vGX;V*l!S_$5I6iym>W7m_zm4&IA5ayFeqfP4d z#7<;Zwum_4+Q^z|NJE{udDf4dcip{<JJ}!S2Hff1XI)2qZd%kMAKOg+;#$cem$Qs% z4`+9#hre2yIG>6g?_?<X&SS-`oYX(<STC_{IJQ9JZ;&v+YU`OsN}~N;)J=-exO@E6 zC-ryD7D3}KEe{+1@Ap$&tiB^bRj~5^j-cFDWYPPNtD$k|{|N2S0#ftF`^VYPNS1|; z$Uyx5pPT`G9qWPt;Uy*Xat1ZF)Cg?Ux)IRi^Se+k`A6Uv&bO5nee4bXEK?eldNNXR zWQB-i?*15L0)sDZYd+lg^h*9eZI>hmV&CQmzCwkrw)>tB_~5vtr5(2C_cAT4%0GYF zuPLN*nC8`*j)a<u_Oz28uXScWLoOAEvjF_+6*{x_p@gh@IU%To7dNeOfSZ)H(@Y-W z&$Y&VC>Z&k*M|xj!I^IxhhcGc<DQXPd{w>lbs~pHEg(yOo^myn)BHGar-`Be=S}yl z=-zndY-Mln%jF&@&Th60KiltmR+DrG<1OF$_4==%9xh~XZ1Q*S%VIrVFx%=^`Cg39 zryE^Xv9fN=5jCEnMy2GS2wXajo)(uQKI0NR!z)y_Hms)w^X!_Zl~%VpFFH#XJbz5N zGDoa)c5j5EMHV`nj3jvR;nHXSY}bGC+~}>92A(*a91KaPVhVczyvNh^T&eTHlv=q4 z%Wym;JU){Kb9F$}88qs7-YFGGV_U6t01_G1hrg!3<#(NEa@^WqZU$a;zC6WE6)S>H zPdqhZ1qkILvY_h$%XH&ly4bSI9~NRrg|Y|AF8)rZc)_kL9_;b6gaAK=m@CrveuuEz zV`n(A6#A+Xc!9fEh2nvqTU}Z9hQ>4bJ>JwRWb&!inaN|OoOL|Ls#SeA)Gtw@wEF#n zsKI98+n8pBPG3wFqk7pyB9rERakg(B|5@AjEbj}MEKh&h4->vGPi|>~K76)|^%3mR zcl-HqQ$d)5`}AmWv^_&fEV>qh@s#y0M;a|hm8~TbQ3QA;luBaNlz=`?Zi@*DxXZyo zY0vGiQqnQ>*f)r$E3Mo{DRrVDC^MCx`E?rY-p0J;PuhC?t!wYGSyBA+#|46_6m*7W z+~<4kx2yTFZ}~48T>T(-j)zN)Y(kA(Rx=c{`MZ0~%K4HrZ63f_T+-1jK>FBt7C?@z z{R&4BP`b)_eZ25};!XAUa?Pr#0dl|5p5+pQ2?t@Z9GwO$vSL>w|D~<~_*CTKs>2~* zDvNe4jmz0;=2H!$;(C+kwUy^?s<{Ptv(NeN+=!Uhselgh#RKP<_2+aY&*qMEiLhzL zXO8Z1@3YnREF;QZ@|UuyYc#I7H}3<tjsY`2Q0z(F4viu;Fs0@TCs{=mF2*c90swT+ z3w=Wdmr(EY4Po}0T<uRDCzkE|q?OAikIjaMDFW|uM>@%NdjVX;n>AI8@1=~~F}duQ zn<lpqAu}&!fnQz%pMuU$XmWSfJ71ooG|<r$bzIW2y-s;Ch*kL?vQ(%h%S1-zDs&so zfSHJ%CE5l(F>YDj_dCgCorYO#1|1e>xn{G}v8GaCF<a0z%QH&ud-rPwBeb@?Z%zsL z^l<I-@ZRlIPs)hC?`_5J^t&r~U$Nui?^fcqUSr5MKFCvcbP6M|kV#NnVz$B)a4E}H zb_HXi)bH6J_{~V;T+B3dSXjuROk!km0@&7pS7in8Ixg36`9Su8i)QTBS%SQ*zL!pq z?tZtvtt$)nWfNDCiTifVPbe(Po8Mtw*g2W?^IeN^U*sg6LdH6<_bAhCH9{k5J5T6t z9UcT|h-Y0aaaT^YTWw2avT739!I;EfgGTDGHSxpk8F_^#^JKQ)b8|j>8-CTHy2qyX z;Y{hiQBNq?C6<&oPkHjSZpOB*=tzJ9HF2rCT^I4j<_olaga%;JqN1P+_SO-BJY1t* z2duLbG1+n0UhBow7v*7?@M`oI^)fWm)spn$&so@%e~qx19oey72Ijfv>KPHflxdC_ zS<oVNQo0(IC=<kyYX#*IgnJOPr-gj>m@=Xw;|UY$K-hKNtS9ZtcF<`1EtiOo{PmpD z-$)W^*FQ0@(D&&9%;l@bv;btK`$g%$(J%CT7X;sP{&}^{!$Vr(8pfSoVULix?cuPz zjY<P^Y-2SPoy5WLSEMW2OCp16D4Fk_QAe?)#9jCSLb%Fd0-YjRx6dqlK6Jf<CpbyU z$XnU#0qT~Q43J;(w4tz+T7bA&kc{#NYjwv<Ag9^r8{gRY-?VZ`=Xi04r56APf5WGc z1-w}GtZEY>--Ns!Z=cNwy7@$vsbKYqHyt5u_q*w8ZSIa_X}csCe(c-m9{agrB_ctu zN&83GI!ABUZ*#@MY<h&XLj|s9RMlEtZCTUHz7E}9#Z6xXU+}ves@+Jn%2!HV4)t!! z(nL>J=s1w_GtW;X^6i!>Asgx|(KI2jvsJySIut%_+QMMetR&3_rEx{YS<tWeKAw1I zeYDk40WfGdN!~UU)5@oPcu0sVYmBahVf!7R(v|lXQm+5TbTsAb2@kTER~i0DhtH#Q zOv|}#g|tesLPZ5_Y5rleljk|wfqAib1dg{N(;TyQt-}@)h_LtY8c;4Ud3k#r5tO}T z*BWC>v>*ny`qj;hG>mbfJ)oGL{`R_@*W!mIG~uNS?r_mQbTgy9O=2~###E4W)XAD~ z$BLj_*N-EF$5H5oA_-qwPJU={+r``3=;068FU{GTF4tt!kN!UD(5qmFG>lIS7(@*} zB{FoxyC`+s>Q}3`Q2r>kQy3b5uZM13JJlERz3+XAe@zNTyCw6W2qp=t14R>gHv4%j z4?<M9QfRdUswvWomEgCr?$ZIw=d}50d*mGD;162{=nKHKYf<WGij&2Lk?&V~*?w7s z0v>kD0yQJJ`9AF_!|tFVUc3TUU1+RP^DB!D#3r(m?uWi2P&C>gL-440Wv@IRl6yqT zyeppn%2E>uT0|A(V^P$Ik~HzKa54JUsiC0v9{7g~c#bX~yWd~zSU}QKj~=V@^|f|+ zsYY>ApL$|(y&U51@lQGzus0a1T~*<YEKko!*$9*l?&-L^dx0X-{Lk-izs)0nKaObn ztYu#iMmkM2SZnT9F;-hJ&-}s$zu|MHHxJX{9hqyIE0Dnj@$QDcuPWJdhXyy@Bl`g2 zBeasSE=_t=hUR<3zVW~Wa@m~Fqn+{UH>9PmP*=LgkzP)oV?Kw8V*MPquge#@Mul7w zG<-{)M&jI_-ii}CHS+hA<gyUh?U#_Z;$rhM&DfIYSlAh=x11JuE^j2vn?0`?@N>7& z(<|I6>rrCbyid}RKXKe@XCECRWjjRuOonX$2t#{)PGU?LdN}5}3Da5ccP<73FDB9f z2Fi&eNsAURAs?nm9(*aT5Qmero2PG|82l8jNOYW&0v=Ssyz>6IUg4TfYHeB)5pX4h znx%7CcRKbtx%!{M%;Ek3h{=YuF<$S=*ahNCAPaqdKMJ~|5<tf82NkOo8Ovgcw-H8R zd8h|(nQJ6r60`{SN0qK?yp-o|STs=MKN=DCCorhazuiQJ1<tl!-@Ov5(UQBqnHQKB zLK1qjV>neHQ-g<?fUSXL(B?i_XDD6e)PGr|Z34z$-qfkuh6WC`RIEEhoX6|t7<`A= zaX4G6HP^IP;jSD|?%@?%XGde%*Jw6I%o9P|p~02^D?ojgkA-Jb9(fjM+NjXoC91H( zBgQ*vuaN_Wt6@^q0sDgc2yNDm5gmc$q+R624?PirPP&*yFbIi;s>;agA&BmCW3tOX z!>ZY(0yYGgM4D1aYGgta2_weQdNj4(1o4WZaUJsJ^(MvRF{sS%rVH&bChQ2S*B5AT zU9#6@aemlE42}3|p29tv%2_-)2yQ;)0y_XJ^_u6&&p9JIjSP`UNdOdr{T{;k*}r%& z&3L=wnWmyWoZG%zX;v*1t}!Z}XOs<R_nfQ0JBB=8&lz|uL3MKD5v>Ha_9S?Z;yu$7 zh=>4tc3hL8XAh5oHR0>kr@rnF0=i|Bzgl2Wk-=nk*@8Yuz!e=Foh&FF3jeP96yR_& z93XTZkNYAHK2)I|$ia2E6p@QXug)>o@`p=sS|_pT2}SpSN1rv>9aFnnX51~fmNi~u z&0K3hg-`F<4{@8vlWeEj)6NLxHMb$yN@~)^z$%8;kyDmRr>Ks*|5#J7X4VWbT(>9_ z{bzc^<L3g+k=B4g)@ZNO=CB!Wu2LgYm7b*^&a>5=XQMksQ!dmj5kr^<;CDsKvc`@s zbs<cE)m~km0@C@tOdY-6p7xuMTlPJyXl;!cH6%$f_Go`5BgS*<z5`gjOTuxa`$N@` zaA3FiI|5LqSfhm*mW2!HGM*B1cOAyxMx_7CX=!+&)bG1ql5F6=e1;+eO8wCHmcKS3 z!~P*K!r!Q|a_L6LR9*u=c@AJ--`IcX1d*HMJ=J(+|1JgGMc#typLjOb+KkNYb`?&; z5Mf&(LCr!tYyv8@HRs8~8BXzC4}ELreZKBkT~snmlRLc%pe{^kTxCD>vFa6rqh2-{ z3!_v^gjo149!k!<$QzmkJ<~@TupGp@3Lv~rOiA6@{xZ*tvm~y&8`<aEZ^c?6)4LCE z*maPZ{anFO)w-px(J>VRZ83#ai#{FDFZ=%@jDuI`8gs%wAC!fGrKZ@SFtor;9I#7- zr&%n0Mhfx$&bunE&P-eTAK9J%Lw1jO>A{5m@UH-I31K|@ykXXeAOE??73v~fFAQ18 zzp@R5GB#FUdP%Ju71JLVW5GpaLeYP3RuImAvW>JFl+e8^JN`C^@~;n#>HcNECRgJk z3a-apk4g&1uxXiq`I=>qUM!(`e3=Y{D+M-3WBWk^zhQM!zir7piuEr8R1Am_R7|*d zGITpIuuY{x8A(jVHyOPk?>)xvnf%8kRR7C-DSgjT+2o>VSmBaPyW%^g6AM?;hmxG) zn=T9n2<~@j{EXJdvu@-L;pMr(q%N!4eOh~+P)VlYLzL7L+{<zI?CygFK^RR95MOZJ zrf~~dN5y|52>^qB6_x~o0(K~)$OUf=sD`2u@nB{aQNICiA$dIAkqEUyghzhMbo|On z-=WC3yJtk)z!R#XT5oo~ymx3NrIGaQ7!H`;8?*Jw-Q24urSPo<;?sxU$*(`%t`#rT znd8P%4JG?01Z}6^_9w{kvQJ4b?{Nr{6y1mYKB+e9X(Hry-dEy-!rhd9wLE-jI28;y z8E)_$%MJy2JMnu;<fe-#1A^;BOJ6k#g4DrvC<Sgeg){@AQ9QB{)~kR1vr3BKRYkF{ zT*x-sRp~W%>u=Jx>o@MdBl9aDrO9eT`SPid6yckl1%#lFg!nWmmsq!PG4INgD`Fd% z;1utDvB+4cg7IUYMuAhg%_zy<Vg-YUBUp3WbSy2=B#c_)#TNn|;Fu#ZyT>otm*z#n zTj<@XN(kyrG*B>MikxF8rC1>0G(%%rfP$|{D0$^kBSpj@9lV7ya*~1+Y4%(iLrn}r z)^AtH^NH}Km^kJ{(z&7RCK~4{y=y18of!{d`%U*`Ie+v!R1L^EM)~`wZWiCwKFQ5J zd79lt20~7+)g2109S}d48Tj%i9r#irm_8TMHWHxvviy_oKD`<e<L^|5W3z-*vxMAl z>xFbVRVc@!6}hy*ca8ZKMFSG;ffozrG>h*ANpJmbR%O+=iQa(`L@bA_$V!ZUtZe#O zj%g^hiHK6u$LMFW^kL7O8SxLTAlqcTKgu*2&yEWp92{(_M0dN=Z*k>EYqnLC64{jc z@?|}t?tB`-m>ic=$b@&I^uk3kePjPayWP^XWBi>Z&U>lij0PqHNc3&S!`@Fo`JE*v z=?cbNsUIJPq{c%Qcz;Hy@nnd!>OJAM-haZ0u>YUj#BK=I|Er{Yg(`Z~=)C_fTv$ph zL+DC{N7HKiYcQmth@6ua8~>}fBBVf9Dhxcq*<ZsO<fdYGmc|WsgafE78u5QuO)b9B zi%9r|TfD*7V1B-ss0zKPC(K<_<cP}1Y<6{f>UUW+8fqH(_K}mU!O!(({WY79^`oRv z^q%YPqMdQ<+HAR|3x|Nf6)Knvo}A7w=&kh+@yp9Z@_AY*Ipf{pY5#n6&zKtrGP@m+ z76z73fkZLlTu1M?Uh7xLEDhB0!=T6Jsb|{l`&nO#qW;VI_i;Y7_ay0I9VqynNN-`e zV@pb65*yfrpc(g}7PrJJ;=M1FwC8-VoI*f}DD6aY`xW@AW)zpYQwv#%ux0rmztMxD z3bz}i$y=h|&c~utKiEJ|N8?*5(cPWj`=)cT!FqbLFN#HFCmVZM#GCg0#iat%6wIWQ z{iU#e>kwPFofKpJJeFbg2`t6WLR8l#g=vx>cOIHQ6mIn-Ak_^SE8{F?&mMcw7*sfU z6-D0tUe-7!+l^N^t?hC!MZxFvPw<G{T|jV{K3C5g-?Ui*w<d=!k6b%=uQ9<15UyQ~ z(PmZO(=l*=Owcm+Kgs-0{%wQGyw`A4$^WK%3IsSAXnv<Sh+O+?_`lJBXR#UO47DHo z+DfPXVLHzZ=6y$9(O9%p@y&Yw!MF&H5cP)c9`a^!+xt_`oeygni(<^UctQ+*8Ix(; z26uM>%>v|tT_w7|82!K=u~=J%eIRM6WV%*Qy-o81*P~^I&bu?cLg$*r;$hr8-=^zH zlXaIrqUXqbyIh1ppf>TPboNnx>fRqo^O*LoW81zP3#Bs$%qfWaphu2NfQ&l#t&_lk z^v$c3>|?#1L_`HQvVgjR4q%ky9DA`|#-bbsBwAvGfw1_deyv|%>K6Ls&&{@&j?#yq zFxByX0jFW@JEDVCK1D{zm)Kj4tl_`Hdw@Sw-XwA8#rbbb11UfkTK!gyDb4@77fu1{ z?=EaPn7;-)X#Mnx3d#5vVL(9&4^(%Dm12hdSC{w4YJyhaGZupX**T$DM;LXhY-#3< zU%h{Qrl=hubh93p=~r#i42<f<3}>c=COo(0Uve+5_q)z}1mXg@V9s}fyh%a@2A3ae z*MS~_n-t&<>>n3<Po$J&t|07V1SVf3xhPge93f~H-FJQZCW?<D7ZWKOgfe%B<9<xv zr^nk+%=vq~bA|5bq!1$aRLu*<MCyyeM4>eyDZYk*<9DxrXA^&gEUdXg@HLiW<!g-7 z^4B=oT^7ZVT6}K#Ce`+xnJm?(B+&+&z%us}oN7CeicYgNY@sZX2rnl=&JQ9d)zBpl z<+Si6pEF+TNVWeYWeQ$wf4)|y6RONs=TL<#{!db%)BGduO67)DE9JsK%w7?r;<$SF z#*>hkO#o>_+^$Vp-ba50840JPd>f*R{SPC5m1f~KqzoRna97eN)tIxjoCh}{iDqh4 zcZj2IF5fj9Gu~N}*pWs5LsbbR6H_JkxDcSUE&eyPk|4N}zodUW{VXca_%m0<gRv%A z0l#qRX7j~`id&jqD973F8OQ-pYWsTS^x2qk^}jt88@T-$(<T<qBa#%09g-ythf2{0 zKJ3usNvMWUkVU_;zTFs1^#?Xd|3qL9LH2=fhkwFlL@wRz%U}&nHWlPnTVUJx2R3~p zgxx3Ef2*&Gmsb$>32c1LL7*~}VHfVCABg{FZPTd2{cuZoUZ}R$mPQKx1l~-f{Gm_q zciCgvz)c91yRo6TR+H*W<FS1l=<;ge=q3FokSzR9@*m2Axl$}s!$$DIvxx|gjI2e& zT}l#BDXRZH`8?kft2R$6_SYA}B}l@UBIKr>ELEm>kV*UVK?MUzaY;#?*Za<>%znp4 zB!hX)j9Q^gp?@PBP6mdb`q}FX{3~5d9XAvB1molOXj=-2e=)W&Qg9^971ot<H7!jf zA-O=;>Il`}o}v;ko=6Y$^EfztQ87Qj_{i9QQ23v<l}ok0ty&xqt}6}AUN}^~|6)t1 zBhmh#%}~8>JpZB1Z<Nr@nxQX+H2*z64m|8Fo_&4KHebWu1b@xn?MR9ogp5O$KRbSp zjb>kea?YJrt~DuWeF*Vmay-mVBsc!su!{xG)pN+;$*dUh^Aj1HM)*R7#!pT+)Pldf zMikoOd7qmfK=_qWopHqdK+|uz-kd1s&EH2vqJrhVCP}Ew^n)hn*Vx|>X#bs(C`Anp z6iRCW6*m9dwTt-XUk+@8ev0cL@laIzUVG~EX#Eg>Ka7?7D**MS0^;^8wD@qVp--n= zBP$VyxChECw$(Kj{2cn|dMQN~=9{6{>KXIAct_aU)H=1|{IG4gSZ9rCI+*e`GLiW( zOwm{!8sPj;m~es=;ZaNHj~@r(^33A>1OZ!fFiLQ5*epOW8|)U-+98h)(Kx+*r*B&i z?)xk5p0gi6s)$?Nk}uUMn;$HMvhYT`wEmtt6w*+Qiv^TQo*-KG_F{V*_(I>6Lzq7r zS8I>5_upV#5BGnRH<$VqNlO`jg=DkP7V^%Q_p7VTl1yr?xJsebtx|ey1p^dgXJ}nz zbP{o=Cb8)#luj2E#a21zoY6fA-wnEU#yvc|`Z9LG^#QmTSMLY%WmIWHO03e$BD03j z7e-1;r2n?hBTv*`KruZyoGq`ncs3sX&W1T_t(DBEUeUWjF<OdSG&L0>cW~o;P~yAw z@N?sAx#Rmg@Am<&Pp#<}YpHRETxB4DF%QJ(!g;aHh!gPp>b*{@G5T8}&%jl(nStMm zH53TnsG+%f==~h+SS7AjqgAFkkCs=5HyA~ucB|M=f<d*Y0Q1pD`g^~3evIWt6)IqF zHPWp=frzXQ-V4)x+!5~8+(X32EH5Ol!#P#Uv�Fordvx^F6Y(yadMQ7!GZQr^F?V zGLNOJE9IBCed}52iHoBj2G^yat)HEZ%F~OA?xP=nEC|<iWXr>cqU97Ra_8<uCF4Tw zOn2FWn-4j5M%<5wBcRKr2Dw0-NkIQ)u5!A;dQRZwyvN<v6~l{4l%f>-u6j)>qd<zx zE100tKsdjHXlPC;vJ>?9`5Gq9E5WYnxoR@{)$~C?8fJ>I`_YbI!HX37BUMtnX(wAg z|J0fYo3oV|$*pZpWtE%hrTU0QUu{^_kdTiUr(d=RFL3Wmox~;KWDWIzfNZ1f-cQnG z##{B+t>5ljb-%JNP4nqUCL=3G4zAtw_l|PUFbj{$SSR2Lb*qd3hpVmW9kf!EST_ip z`&|h8+?*@sZR9UxRSy}J(8>?5R#iVE6&ElUk=dN=(u@aZq_{p+IFg0`i^ae@DUlR! z<q2~|(U18d=vnWPu28k78H;{&Z)t#eCUCP?sfz6Y?1~9G5kthl_AJ-o>zMjlrcos; zN5EXDx!5VIv!Sm6W=9E498qT+Xe?Vb2=OX8M-}VWX}LZe?wJPF`y^?UZ&-6KG`Uif zywx~G?Bq;|HybR)Eq1@=G$_e7PZY9mThcWcRz`yE<s#46yLu|PR?24di5kC^L}2~g zwjUw?+eRC(w*WNT175+g>4(>R7Zmf1Yo4{1Ng??im}{E9`D8ccHdAHp_pwr`8BSfJ zeOBU<!15@|B*{iwU@t}Wx6`gOUiPl)Wqk^6Xac=@Mx2?DQgyr-BIa~Ry5(fg5H@z> z;#mj^;TNw9Y6Kiy*w)OV<<(!x8eDWmQ<RSAa&V~}=9^i}RE<stva!^wQJ-rKk+auI z6O=pP?(ZP`8~MQ!T3X8=+-?Lq_Rc_e%DD5$yP9-kY0{>{i8mur0{2dJKBI5R=iTB# zP#G}nM}bzUvCT4M!R&*}I3Dp!kLFb#aZ$hDxInXjLIc(9ay%|Hdv9Vj&8o$i&~QUj z!}+_MwLPxY0RgL4R<^0?fb5A@`!=a4KV8wKmxb}3>gu^1OZedoT3pP>=jKyIEB!qg zQG2ypsu*AB37`@@UIHbfH;;EUTUWD#;N#=PYiorJCW+6X_2e$)zY8C&mdgpGDyp5A z3M5R3XKuwFobD|6%67xKD;EaQk<iRd^mYH#ggu0UfuoGX`A<a%$Gg_m7J2Gn8d=S{ zqf2?_s5_D9+_hiEMqB04NjMX%ewPp7O|N`aSnwOiR{jzGJFv;{4Y98YKKL9n6qi6i z<fr|@_=Z)hnbehRSS;D0w!^E!N40VSvLgl+<3PD55(HqQR(OaJ5X~!pF7|%wHmuXW z`jvUjN*_l8D%3S8!YEv>w)e1~)fnAVj{CcwYHojJ*v-^C(G9^-#&09(-JQXmth6UL z>h#7}YNVxXeeU?eTiiI@uI|1x#LKyNeyXHoxX<-StrZe67j{87s#vPJmezSa7w6M@ zt86yQ-sRAY8?d?3Xijd#$}d$(s*n->mJ;hXLQxfwh&_O;&T5T04_dnD9#lfp#-vAl zD6uf%_J@*~RXJkit~k8hJc7KUL!$7D^sMyp#QNgB$V&(YI#$p>1fMZ_CTDWV0q>Mc zq$%b2S3|WDPndjGZ?<)y4hF(C_E(Rdg#XDy$Tacq<cS{+9~}jly&q;QHCnXPmvbNG zW)9Seo@6NW9a#}@@L{1c-ygl`of%x=?BUj}kVo8ie|1p6-MonLF0~^e+)#*c@PUGg z)h@Tr(=Sq!-yc<f`7X$@I?!f{c18Qsf9B+HGC)GehS_zf=p_dr`5j*sdc+Z;lK32d zGR5ajS*Wn8=WM(*82!t8n`F95^=s>jp%j8SwicL3MAXdoZ%rNw^k32C-~T0=+G46m zph+8LOdIj<!Gr*ZKfprINpzF)f11*-e)&4~P5NJqf<y?dt39-IG-Ur_V`eDxk|$VT z{{Jz6xVVIb_PeY87!sRGA$v2k1p_a$=Ru!;Z-@o=iw4UE@CKWR$TC&1J-Kcr_U-dx z9hw^2-+3ZL2v=X{pm8BGn9#oY+lhiAQgb)}`fmO_W&i~gf{=*M<0SlNfYvva*MET; z0#spGrRYw|`D+N@Oyr>Wi(bMJmQ~v(F-%Tovc%4M)BcX=kK<6*NV*s`akiEwu?Xek zt-f1)5>ir^ogI5eP+h~oZYS&S%^H#C4`}_aNt|;N#kGhYtdpoDP`W#zXHayT@j3rK za4yAqoNwJU5ZjesMFiM`%Y708mlpg#=b8wi=qer8=U|nzs%mBs@VA#J1wjB!xjWnE zHu2Wy6^UZS%-BT3=5YCSo0)}qZaD9dX1vz>1Q;oiHbshn?*oe8p6O-b+GT%^R~fXD zU9=nwCo%<ls?gjae9PDjq4KycBvU)6^x6j_lBiryVJbZq8|0Zt#rCD2+5-tDo&k?r zCxWDJ164;tLCPmyWVtK+w@L@grbvk*IXms#tHo@gJ{z1VglUb1<nI)3EqBkxpq0om zE&e0-_Q*#5!S#4m>xO#U(;aXnyWVIl)V<X$h|S&A{U<eBo^y|oK?HI_XGb^6Y&*#$ zH`0NC=UoYi-doTep?&g1KHIE69bA!qo{?*p@zi6J@8(2C11BR3HWq9Fmb2=m>{UV% zp9LX=PxZ%iE$3^m4xcwiz>*L5>mbXKT^6t_c`QcbOI;=tm^M9G=~STEqB|O#z@fe$ z6|`a{T+F<3Ar*%BVmJRoZN?SeCF6G5`@!P@gnS{GtM|H1(0|d*dQbcEtJ2FT4lcdl zSU2j;(@d%AG-{@Pj@z0$$b2Fu%SQa9rEPzD)Sa`#Wm-{oqhqE5z-7O%yq}qY7$LlG ztwV_0;=049(`fY;{SBZ0#Y$?N535=e#r0zeTOxx&D64*x^qTKAOAN<V)4RCzPbNYn zBd(L4HkaZgWh(WI*QZnI<JtU**%%KA(2T4Q^x!Mal=NfWNTsPtyM_RubR*K?cQ9qz z;(gE2Q1XH&PYQ@go3R>hAA({hRZuKofh@Kfk=OAU;dXeVV@363KfCjJ<B=yXfZeWF z^B8gM1H|TK&)|j-9OU=tdS;@ER(j-4jnSf6@6lt1VZI&zm2_>_C5z@NZQ{|dcn-#O zsomC%5BPwF{<5yx;`k)_=^->WZ=j4jht;-aiZI(-#zgRK&+&RA8LzO(>$zfutlc}W z0WT<C{DZZ5PWBndxT1NE=YM`5i;taqo{REEccVUC1YE#5Mpt!Jy)+ztq823kMcUFn z5%arJZ-n_PqBQlFovFmDn{xZ<E<t$SRl~#b0q^TWb>fRvr83<LvVen(RN#F3w1eRS zv(NN9g?vRJNVL~nIEiPnVRY^NO2L`M@7ucOw!qX_^B&?Di^(<#Qp94RPS8{4I);b8 z_iq5(C-~@#uP^6rOKVH}RWW|bV=Y`;2wO~mb6nFe13%xAJ-WwsJYQIMWbwMZO9nbX zj`KlJ#E!7VP(a)ZV#t!n06kigCF>@v^mVd;gDnX#rh+Elv$O9uEchh+@&!Bs7rHDa zZ`UB}$R^KhosQaFPsj$J4E-8%weMVr1w`Ey&LN&7Wv1m8tDwvKkHa*FEWOyKo9*0d zdwVmjb{tH}gR0v-(F~kCAfdw-><ayk(4x|dV0YcZksssl%X`|UDd#$n3$A?l9akk_ z$G@?liYyQpN`tAc`dizfq@ZznLriLa;yH+Hi@MB-$NQWe)R-gcTXwB_H8gKj?=td{ z>-Zj?%i@NI3B1dat{lNZ+jJiL=uO%v2`&AiKyy&xWZ7nH2liMZ4&wmEib38?DRqL1 zt>OU`F+T|%N_CbD6D7XYxln>L&(GWM0U;`vw#up2Nu^CUmjIVJv#ZEH20tF7f;>0F zap`Y<D*h}Kl|Y#sl%d`av=WcLw?Q+?+U+Aap+B*0oGh`IRJjg2El$ol5H428*bMEd zs`D+Z@Utzv4Q($!E+~vcM9Uo<{klBWF4kAE#q@SRFKtgG=vA%rFBJ{QXD^rBDYs-5 z6b~NTwFkD(sy!1W&d|+);Tv}5m?LXhWD#VNtr(OXdyubMr!s(;%aSHj!0}su93*mO z199}U+>!12J_nQAEVTV4VgQXUMa=tfvN)CdjZ|htUZrhIU+os?6|s`irxJ0`YrmM^ z<r|9{@;a(314k;4l_$J0N;t)dfjwBH#M1QegRPWj7!kO5c+^wphq_f0z!M!6%vmIz zd`6Dq4Xwhn`LA0)?oQi|6b?DhSsuKm*FLy(<1uQ2?L2dK;nf;E)jhXsq<FGg-Hw$# zDv4#}$+tK{YS+bG?bc*6b=`6w$y?(3KfSHMTjkQgqfa-x$MjRifKp+}QYw``lzwIa zl&@^RCK(1Gpc~Myd^45(ha!%h+AG(YXXW^k-xMCKDHOAX4Seu7_ONq;RZw#9K;6z) zsB+!Dgl%6d90xp0;g_ow+^%_Mf7S_^?wsQv_R%+sXOa5&P_h<w+<*KW9w+5Q4QHA! zQBE+^C}m(5Zu}ORxcZu8u9z;%vMg()CPUEO4BvOL-m)~OtUTvS`zx2JthcTAH<f;F zKu}Yu@oc3vzo@y<Q`xoS;k!B~nZraCfpYjq?=H}`i9CtYhH=P&GQL3qQ=N0_EU$)3 ziW}edA~&q&+Sc`3@$=Ixb$sXk!Dujw5*vpwZ6JzW?_4(U6&h{UX{@ue=42~9Fln@G z4h#U?8q0aMj7^g&6l?N3DxzQ_AfR+C!p|n;wRKkv&pq7G&^Lu03W$5TovDlcemNIo zx4+gKP8VWZk|?ZmT9CE-&=vO;S4t^B^p(Tm*IB<GW~L9jUiK5VjcbxnCvTEror+nG z>T^W(h!k=<j?$dAn~p<|Kwv_gDT-lP*8XrX_w}e`#{S4Ay+WrNPZzm_%_oC6g&zs) z?$ZzQSv>QZ`|e`O9e{mX&+W;@WnCt%ruf5+rya*wcK){IfU4Xdp=GOEOF<9rqkdWU zQL+XKhk$h#-$WMc8=D#7YvYMw$yY=M_ri`at)`tXBCYC$uL~n2M9JHpU{BV(tTD)= zrwe4nJT7)}brP)xbo}8S-%#GK*I||@1o4sLM+fKZomUT=gn#Cthc`P2dX!aXDt)ai zmEq(eQpN8zew-jDkF3|>95K@W%LlP37m9kVX5B5M63^Phq?qTwj)9KJeW|uZs-D~D z%@oxoNd(S$-YUnaltb38&&GLnaLBZFG{t5eGWos8n4)*&l7K{2S`Ab#>M?rsrRGch zAK3YCkD-N+{79xz9%sHubO&XodGF;8f;%hht;3a;3qdK7M9nI-)~uRLKvtFS5edQq zd7ZX3tKT2VdAat}^VK~z9L^RbF?KXx>~2a5z)A9oQa3_{o7o9Rl33JXp#-8{M(-3t zWt8)^ibhEU2T(FaW}MKZe+>IdwPuW~dPh}lv0daSo}~_}GM%szH<#UH3W(Emynq<& zrW~usJIMUmVvy2y)g@N_ZKz~!H~?0PU-RCZNUI@lMo>Kq>q=I=wVB8kv^CN9-Mj3% zRZ3lj{*FFeN2yuN=%Y_dM8^)Ve)~dDPtRPPJtkTIFC`2zn$Pb&n9FemEK*1&zbQ7P zT(iGD&8-bBkWUZQO;l=mKWC}l=bnkN{A2w{t#st}8cND0b;Eq@3sLzkVDnwj`d~$x z*g7XO*|$S~mt!EB?I&g^C8FT5LBxAtzX|$yKAb`PyzOo{Uy|>yPAA*|{X3@&iu;rv zBMnD$#w#5CB-E(7iB2A>EEpp0N*rmZq$}mb?{s7nSjSzXci>PLy6Qh=)h-f~Pu7K3 zZN5Sr!x*T=AFj@Z%0ORSJ&m;A&r$?Th2*FN+NkD}>p)MbzY*kS{aH3wO}6HS6RZBP zF7q6CzrY6hG8mu5`l#g*8A=u^Ln5>sEcHdF!7t0plPrvY4v9QcQ|>g~l)!X?@ny1y zRjnY$b(dGC5HaRER6W^(Ku-47N`_SB-F@GH7$hm9qYk1?=eL^sBEY;NL9JS)n>Hku z#aPE5zu;p?=6Sk$#<1f>m(qX8qcpo!)@W&;&n(VTDr#WXL}!aGvt-cS&}eQzA-}mS z>F364&&=_i3T!zko7)fl+#I~rs($u6Ipt41{bhmKtNC_!<fYdEr1j$9ja#yId}S!! zmRo{e27TFNdpf*Fe-8KejxUe27$ol}ivIqgB?tBDMN^-^FdmoQ++pF~{i+LBa_Nr$ zA@8;tKJzXwyW90BDrlmgi^OC60LVobzG0YecsE;OaeKU29(sIwt0j}lc*CACu)Zr3 z8clR(GgQ<^W{TVwI&6O%+XMtG`uu7|aiWEL6_6`9+F<P7*rcKdJ<ztkPV4Z6hd|+a z|FVo&w4)@n&4iKiByI&;mY6B9VINV)ARi1Pl42I9kGuP;cq~&=>BsatUqWl;i38Qf zUF%y$-~A0D14B^Bn_Q|4b{_#mNA+txSDVLV5;8NR#g({`4UGk^mKeUbJI&(D6xwyB z*UQuQxA(yy_j<V3DIL;<j5|SAC_OSft_N~EhF?i`-K7-ozm2dAa}!6sv77|?({&3f zL?$s0Ph&1hSK-s^<RNiSjb4yX)6vEDspDuIz+!Ii*jtIiTu!fnUSfknE-aRsSzKBc z>f%%mz^vAMzGeeRRh0Gy>AR^RL93)idIT@c;jSfQdtbQ19)6G5V-uJ@v~(Jd+0V#v z{j@6ee6@qKlVeqr@|s5SEygkbe#AS^NuAN|xm|NL6OPV9_Ia5yq@%LMGCiwKpV{^j z?$IezjXlE_)Xj2?X(QT){4z3}yfyEdj~vRLRQQ^n$bh`r?Ox1z)C6H<qg9M3#Oir? zAYMvq@9?iATV0%P$q{T+>+>ALLJme2B*t_zm$d1co`I6#C-u7*$O6!Y;45RG*JM+P zb``-`#tro3BN@f0=`+U`VVQm%-xBabwbn-FXQ(U^igO|{qi?M~DpvvU$mn`UF@zCc zOWE&s>{j54*J_{8>e(JOu}Uqg!bJa#tjEIH$lc=n;muC=LOWD{+#rLw>v|zq*P%S% zT>iCT0vn}YU<N&kB{GT1mg`BO^ksUstrXl2$}W28MeXS#MO7pFaV^XYJ#1dy+XMD{ zDh(}Gx8<`O6(>vkluAXrH$9h6$ML8YtJfl&k8<gh;(6x^!6SRme)Dl6UCJK2r{N7t zK&@@LKC1S$)UpR(9ldrTXcYZ4zinS~#Gzpy6Bw}o{8IdyJB&hkH74)^MuWaw64ss> zqTA^SSbO&92MOQ@W_4&k_ZT}SX1@W030ki{Sgk6JHDhdt;_kXpzPZ-QA&7=b(;0I} zZRNY>F-I_lh{Kbb5TjkSAKkwMUZz4u)D)Xj%D4|pj36UcEInLVFSi|jGU#BF?PD_a zkJIJKNCJd!#zdO?OR%!|Kg>_MzX5nh(|Kxa^x%X>gMajDI<MfZIYnHh{aNreWj*Jg z5HIed+35J)Tw6xSpUn0MM=R2mU2D+AfTL1v6$Ai7$JnGKHbJ?u>?rMzFB8;UGePgu z4mB!9#B%m+MPw6MBxcDg$C=dM>xJi*-`Zg`ufCi=&Ac?Yx!)0^h88KNj^C_fRNTLo zC+jJ#$iWZx*!-3MS~U&>U~$eLv><=Gy5e3Us~OxOAPU-O@CD2U#`*?p@?o7mX17UU zmk|b1n)lkD^OFNj*$oH%c~A}8p2YPwZ^px`ORTozl}8at*=^9hs@IB4tjRw1PEhWi z*1I>J9&$rP|8Ot4)d3B5^4P>lE%(<VQO(ajaTmOdfHuC|SH3HnTY_AClaWlq%TfZb z;~?SF!K({7jfl$M3FN)|*Y@KC-JE7DyBz3NvwN2bLUCQ1*cD5kDYejMa!sYmG&c1+ z`u9;b>&*IyJAOXY$YrxNGT438P}X?giAJVnK2fxQYK8wK?izTnQZZ9cuBl##qM=os zqj^v5J>R1?42;Rz>&GKlF_MSxWqvUVjeK*)X`&`^>GUmY(W;Uha8YEHqAhoXQDND$ z7^8N!`rwVa<1wF-yO{FS=?!+uxbtGZ<9UChWEkB9%?^^1dvkoM#e01373o(rXX&xC zys~dOO}AeqYEcJpsOU53{&;mzM4(=AXU>~jv#=9Vsb|Ly=eq4M8Xm8G7fzn=QNx>% zk$5_&7m&a2&Aoal$o*U1Y-eg~*X(|FZawIl%-wcKvqj^jb=W-9L_KU{42BL=94qDo zC{n_ltccJd$D5!MeJvuV3*EyOOqu?FSbOWJDx<GYTmc28QMx3Q?(UKhq&u&Gv~)K} zNrSYsbY8mQN+aFf-Q8SZ9u&Xt%x~WL<F{tkx{GzW=c%*LuCw=N?<e$!N?3wy=`jn5 zAr80L&a0Cq7Yo^&02oiLgf%%tl?e8Xw08d#B$XJvXD1!TTVG-QjZpH5bWPdXe4jDQ zd*I9^Bxg)?wa(_-To?)Axae&t$r1#chkR8Ya+*9@NDz<#03y5nN7NYpMU0;Q-7h3_ zjRSuc+AW>WPMz$E`iE@MH42xu%o1Zoq~|<$T(nQjUnSX0Ch=svFzZ0I4WkH8U93s_ zicqmwnRaBFwY!?yBu}$|dK9Euj*fTw?qYAMQ>47(ir;e?C-pSl@w-?NvEck7!BJZh zqYLld%WRrEMVev6TxK8MuWD-o-F!Mi!Yf#zWR^!9e3WEP^Cb#RX)g0_F+M`>@JBD* zC`2~rkA@~xmKFgB;3nk-HydnCd0n`urh2*2+H(XvW23PXC*iYMXY{Ci8N-61LXc74 zS~b#Qocg6&^hG*e@{R7@1uq_a{ZXWvy&`YVTj-$-_Amldf$g$pKs!aKw;hXnAA-xy zQM`YSIwuhJjn?OJPgr$nz#~xETUv-qV{UIK25Mhnn1AROiy@}rVLNQz+!4sj3#VVv z7{7PoAYT;s??QqefIa8W!wGm>f*n#U?+GBwzO`J98d0sK@-@=}uldQyEJcw;UuJ!I zt6svnyWahU#?>N`#4{8z$?Ym!Qe(jWirLYIfqzS6MHaE2Ne>M)6x<oIUvx~YRWeK< zG7FSsK(>T%OQ~UV4~KF;P#P6j9ctmHn)kJAH&wxUrY9Y8>zJK|(dm%&W{OoDYP?nn zGGjvYoc(4!TCiaDLZJ?!73cmkRw{#-TbujN&|b!EhjGu+hX0*<dtwp`8;J>criA`t z`S7c|7+V9o$#|w34g0HQ$C`RSj8+<|-|D-7<z+G&NLss6J*|VK$~|OK!r%YOft1~! z&|(G~M$9AQGg@HSg!it)Zc*F%qwFW4B#C3Ph|d8(qChH=L4PB!zd$hQIHoAG82Z7Z z`joIIM5O;sg4<Q|J$IeWo?gia_0m5q$RU~+(O3SLRq+7ag*$UH;Ux`j@~PZyX&jZd z3b*4HQkFC!6b>H0#P<)p6%A(T!xI-u#kw!hyDDX*TOc>zY4Lu-%{pe=9e*L%cABud z9R8#s;pNb8+W;y}|M)%3A`Du?f6oFV1e|P{{WUBgF?o}tZKPkvckV{a1!-2=Rn#V* zZuKrfrv0Stiw-(M+JDu6(!f06F{a-pPB%I3R1qNorIhJk+Q*)+Pi(f;YP!N`HlzP1 zaBG3#byr)=e+~nH3|<5-i;@=1DF*{^43^2Qn8SY|<&wKRh0XMc2!w>?A|*NV5Mp;9 z0D8eg{^Aj-ApFJ|<5OeCsjMGNzkt=sySxZ09rB-dbFxDnjZ&YxruqmlA%Cy+EBc9l ze7a+rr|?@6<pu!iAr}>7ZafwS620=zhm)xJ6Yu^k{6rG_`ne1IHHDJi9k+ahp3?Su z_d4zl<ma&?zl2{|9Ew60M#$d)H90m6j*gftwxsG#vRoD)xhvZWA;rN&6pHv>23KNs zE_RxNLS)j~D&c}$48jZ52k=OQFG5;e52Ewf1E3?6PrF*+Vq+9iBHzHBI<coNN#jVD zrbI<XY19D|%_d^eZKe56@|AV{p49JYRIWz^!!eoHg?TA5xkF~lI*yYpHn@114o_!r z$FD8#r`?QB5Q|a2nn%$S;?RJ2cQmb>R&&=I*@Lv?&g#J<MEI@V_9(lnZPED_g#e2G z6lIFROqw&9Z_iMT?5R_^)-@<`x>9wg)_xn3EVQjYi&eb;;@U8ofw9cTN<eeI-D2)~ zC_qgeL`r7q?lsTzQLCP?Sn(dMstxtEUhrXXp9qL+ZF!Uk8grZjV4_|^G-*SOWNJAp z;UQU2+!vimJZsp)%6--ubdLWPncN)6jYK)zNpAoB<xTHH@o_m)<mMxAX857>+hhZr zDf2+YH*hULT4$cI2Q4t{aV7ru3p_v<7oM|cPUrFYy!t;>bpJOP#L5lHOr#V}<O$%k ziQ**ghcBw628V4AwdJ@C4*!QDb7KZ!h~sB0E*{&x{p-*jUu}^?%eT`4Jg$eZ3}j|E zh91*E(KvvoB0ZPCY+u)H_<n4ZiEdjAWKAKZQxdlr0Pb*jO%#+!7=<sOD)8NlFa8KP zUyHbCx6b_Ckra;n5(V_swjlfk*>b8GmFL~jaTOi3A@I_1?;Ql8o-l8)Ow_Bn3Z8s2 zn|X4HTN>Op;Fyl-@nVG(>YMuR%~|R@>z#O|6x6T3HJ|?L1#q$MWLX;dA88c+e^LSd z(@FYQm-)XWi#L!=;y_bsm;Lv{J2-+pEx!w=m2cB&l6#74wMCAU&8{jc5A0%9XA8h= zIjonk<AS3hi62+5Vy@rruPjYb4HgpxDCU8dVeB*@ZJrwdB7O`A8AMbbkNnB^vrmXK z==bz!2mCbdY-XuA*mUN0xQ0n?v;zE-#_X!R3cGiY*rNyOV~Bre4`=W(&fn&<jQBH~ z>>N^OmLY9t(zy=?UT2yw$I42TG$)F+Z7XbJX3hl}06s5%pOe0ph&uG6NSt!c+=SAy z|6??n)bsA|OoVgkX*q%4<c@3uDlh*4w*Z|h`$S_WXju9_uYV%19D8#z&(`j66SvN4 z>!mV%Wbi&sV!&`_jc{b-c+jLd0D^;*C^u}p9!!v`8cOAnk)P6Y&VApU6#bT2`!Wt8 zF#Ruo`}+RM%SEiPACRAJxJ8Sh`R<1&pq9j(i}E9ZQ#smV22rZLe)&Lz)%^&CT9K1O znL0UjIG^O%f8bFv2wEzN5j%JDvYvaqAEnJ+r@KmR9BuWQ`)vacpxB^@{Uswgpnn(^ z6H&4F3$A<1_r!l@U>AXa%Q`l;c;SQJ$>&V0%An5DeI@xibB4m#2iWE8L!q0|N(H7b z5~@QL->>@recVf_CnwE_D&|?yR2G9$$OmC-vw!adq+KOBl=DZG>VHtqsl64Uf0Xxm zxROgOmuJ_{T;36o)4pc-1BtVi9kTjFYW<jrBo})E3_rhqH;Q4?9_*(SKhl0!_U-Te z5WEG^y;8iJyystJJ=n&M_EHv1hvi>}*zDk&YXj65{+><-2nBKY<CJ8!b?LOvF#dEg zc!P`ftAqt>Uj_ulj_FDb9L|!)WV|)>8)@haPUb=4QCyte2EpqYbk2O)-*#8ZmatH7 zEL{5i2q1)!d}7%ncp!Lhd^9dSKOwW{T+dP;CJ{p&b3U?x&Zo%fFf*TLt7^PAo-tZy zt(>~lE&Wdnyx+*>DS6-t*kbZ2kuVqxMEBbR05XO?*9LEiqGs7Iuz`empKXy~UvvEd z^C18*Qm=XMJN2WBNUwl38ZW@c{jZQYxWp%?19j$|UkPagumNx!7-`7|zU;zA;#dpj zFCNepK>Tf4NJ$ThH^)X$>bOm#f^aeZL9o!MVVrFZ*Jb;j^FSX4WwBGwU;R&mgChS< zYa?11!TfJV=F3f2klfmI{y(U|XPUx4oxr)4Z?gj<EO(#tl)vTuOM{w!NIrB-gT|jb zKn9y2Cz`6rzCz&qFyUQmwpz1H`9Cj4*z4>84tVzI{pZh_Z#{O$kV<C)TS2a|-a(Jz z0pzLC5vqtP_B)<a4gIsu2;Qqe(+f+R-zFa(TU7Wyd#b+l8f)rJBmxJ_%>7j`P;Cu_ zbg|NSuU^8DyTqqVl{oly9(>R086w=$pSKin--g?RI?^O&eTa(PFP59qmz4n+R$>-_ zh$PmW$m$WlfjKyH_DeB2W$e4wweb>uQ)jcr9N=wr2wA<TO)1Hiy_(h^zw)EtyI2D# z<J|^yL$F*P5cwd4K~iG9Cn+mAUUb(ARLugyMotIAJepK-tf=tnJq(2`K}KZj^I=fp z_y{uF7p-iqx7&l2*lS%eov(uZPCR}RLvH;0Qw5}C`6j{~_GV?^&-c=?c^dO~*Ul$f ze{W+-UE+b)i`33}P<ieh@3d%n3St?uaTO_sn;LoYjE@wQj#6%Ng`%*cB^$#pS1zK) z2G49<a?dkPFS3y<Jo;>?J!RQUCr@$f%H?#=;dR6D*`ggR?gPp`_Xx%0&x(h$d3<L! z`rWZEFQ2m&Ec=}5VuuRkEqvNGQe@Im5vWvS{K9muiDpLqbcO+d<dJjRtiFk%R!m88 zY2w4Ak^j-hzj=>$>v-2tHxUMG?FIdUV=|328ruv3WRkBS5&+2ryI=<?aQ=GvfJuvd z#c6v@&gPU0BXngtRbzoklULhwr?<v)uz@>TyH}*UoCO&eQP~R2(P&p_kc*Ww29S?g z`r@cGYxZHL<0gV~vl{H?Y9a${MMVrId))@7d%g@N1N)wNGiJBO?ZbYoz(W7vy+aHq z^u6j_UNG)1YzNvNh`KZwik@o%{a4nNh-6aFPnhdG_V6mdC<IrTPq~d;I$m2GbMkwZ zsPL&YjJylYs&=s(>^HgHUQ1fHG^EW`Fz6W>4b-2!Pc{6wv?8Ugb21t8iX_7L#bw3< z!De%`+Tv38edvyzG+#~C{e%xhO5$hVmq4dU3>*$q9GW>{zu=+XD>LKsmNTXIXZvZ4 zonZtmM?~zo48+8i*Q|ZFE0+6fKi(qn$#$)Wn!R{Qppbst=r$@Kh1I{N!|2lEd2k=D z<8e#KE_^Ua`E8^u)OD-pgTS#lW#_G{WBK>C(EN+Bw91*VkyT>-S(1ieY=)BW6OxY5 z3eeUL@w(z2&w-x6Ph|$^_81Lo2}rhSpT~-`NPdA?bUMVuP~^<Vb&u!bv#q;qnD85| z7n9q90;h>ZC`vBFjYMG5AHwG108+M;rP_wZ5Ew@&)UFK47|xw4pB-rxjlvoj5r=g- ziC}s?g~FDExHZ|xA%yF&H5AYhav6W%d>>t)=4WXxchV4mvcGXs-?Q+lM`J^E;iqeX zz{g;$-gJw-l=UTJwVFbgGBI!FHY<+1^8BUGr`_TCx=S}ig`zC$McK}f-n6^M`r#j# zQhfcz+KrO;&|3<Sz<ABGnRd}AiqwoH;<F8)N8hfIUr?~X1UQ>65x}vN1x)r%vUG;Z zcFJVcg|iv3T%p5!@b(h8?voi1o!pbaEIJ?gN?|cA^)lg&iNriye{$%QDVuAU77?19 zO+>jV{<xInk?9A=PC~b!iLZxsXf8hU3h(<Sgn=m2Krx|`3f0`qNPEqQ15?OMj*)m% zVXjNpW~~biFOFgVcV(XR(L77{5V-ELtBi|sTs#<5Uad}+=#WHioh_Enr!@o7T)`XA z*;>t+o^93SPv2^?PlC*vg+=-H0c5%_&tISmRl$$e#`hA?mhXb47zUg=Jy?AgZK$?+ zXtXXZdT(!K_P>IRPHwY;*PWyB7Z{<LGRZ$MN!CAK9L$Nv9Jv4bgl8KhDj#tX5Vp>x zNxOb5?r~*}r)fFc@D|lw;I3_?Gzps$r~HZ8k)^HD$`TT)z`(6ZU8l?)%rm|XOB5Vo z0chP<|L_#yz53J4xY3`?8JQnrx}B&t01fw_eol+hpSphWE77WMtKH+!dsNx?!enrj zLvr5P=MTO*+s~RBff#Jifi~MZRrcx*t$!uX<*AG<k2V>QSsv(XtJZ9cCs{nJ#GBF_ zl=7oh?8Ys~JJVubk4};(p1qpH^N*$oyG@#xZ|&2tcn{$EkrR*BB3t2e#j@%*o64>b z3UVvo8+NxKHd)P;+nNd2{TPFU*HxG=CB%(8tWg5!B`yHuwEm@y9QRepcByuwjUVIa zO*4gl0-DZT^H0hy9^$aHZB)LNjbt4UADzuh<_kC3WL_D$t~zXjbe;P^Hq^{)+iunT z?)^ibr5UyLZJikz@L|#(LwQq1I&nka3AiNqnY>)A!*K3Zxl)6YdKGGxdXoa1tlN10 z)jX|w`G_L~DwK6l{EN@_be0K(G$b;9fPD4Ubi%Bw(T38pmzcw~4!Kf!i#5n`Hq_9D z9_~Aj_iIxWj_H>$8Y40tg#b>l+ej+6X9QK_*@O7Wqy3UxXsma{AR=p^33k?^n+)9U z&XSUxnv-@uS@_+3lrG8CT8r<Pl5OXhIiW>zYLYQ0$o}*j&%sJy^Puhy{eQ+E0Gr zGI!iDPo=}&jDF2Sex2^$NL;8Mo_YgF+2PKId)GB-4}%W@_RE*@l_lFHE)&&n3_^M{ zErEIqa&DMZk9uE!N3aR5rRV7b8f!b>uqQq^9ZljZ+~BJ=v-eG(k5x0Aj$UDD&nXsh zZ(S*zep$(|Lw}Ct{fQ;m7a_WeK?vNSm>9ogSeU|}bsjtp##YY9D5sRF?}_E3SOPux z9rrJ~<jU`R+x&p)*e9)J1CYrC*EqR4xnDSSglA_+;`=`4FM=!iac}7cRxA`mHstDX z9Eg~0ei~i!PFjCI^c7Sff5SJQ8DH={yX4Ii(VOvl5iWWsSuBy7CHgaBOR>w&2==yp zwqPsS@HSZlUovlmTdxFBly!b=$=rk`<Fg#m8+{goUFWn)9<G<(tAcNsD7E$ZzqPOp zN1SBJ2<|Y*a{tYQL0ub6eW)vCV<=Mo=S-N^e<}j#z2UZjiUA59)n^<$Z(0AkOh6wh z2QuFOU=RJ&pGN#&Y>)qc@2}T+ROe=EMGmwKs$T^NoPTFm_>la#`F;oEuuyTX3YprH zI!P}0=&)bnV^XajkX)O5uSfp~2mB=sBXjmWna{j9+|W8YxuCb}A5KvM3vPBgvU>|f zmANyKHYkQ%;c<`e2h=Y{CDnNB{Rj-4MR9}ye2rGVpbzvOaSWiJayH@Iwn4=@jo(*m zxIYYr;&VuB(@<o#bjnOp&r8M8E0k*G>E8;-AL#sGHz_nbO)*vT$(FsH&hfm6?h?|x zpI;rJEuC+0I)XWwt#{2a!2pWvKQ4Ikq<rFNDzl!W)_}M;6Ku$|1dy>@So5CNc?S_Y zU(02+=)Bw4A>u9xU+=w}*OrxbOT6#-I9+Xoop^US$8q|}Pxu|Xh->|US+WV>SC9m^ zeXSK3>O5%Na=h$`qfK+|S*^6Y*jjYVRh_7cbr3e;v|q;Ap1w2bp)Axq3oz%V<NFq{ zlq<`hmXfU5*=jKCh*1@!3Vw+X@UO<vgpfv^E-IUQK4#-s2JGH+X<tl|^G&1drgs35 zbvw9i@xydQ^$-UOwnCUzt#pR#QH$qkN6+4-)+nwbkHd4w-6?Ee%8@yN*?&!|^fncu zwv^ETr%+`v_1<UU4Dvl3XoFWEd4JHiGey<pNf_41t+buUUYN}t&K@X>p=~{~8E9mD zm!y6=k&UAw!$+rGOd6WRI5+G9Sk;`kx+X&tidkfNhrxsw*=Z=^mRjWW-oO@@J0Iwy zm0mbyBiBFcytTD4GeWq<V%6i)vmQFS){ez$*w%UK)ED%{y?J)(bkoOC(T`L;S8<18 zvug?V^ah%`mC%9e>aWL0exfxpUE$J>0XSc3%6aCWs%NV2q>FxxrPp1=WdlY%=e|F6 zL|!hY8}A&JZe^mbBAq2iwhY&$ZEi-Ob*Xt^j;Gpn7DpBf%H6X<ME5Hrz@6qfC4q0a z)$@j_WJWjmqZs;G&y$$A%|x)6jmzSCGDltHT^fsv<hNI9Z~7k`OS6gM;X%~-cSt=# zP0(F36x7CCbdd!LUdm*U)Azn<{t+)3z#dS95iYT|+_Tedzp4@W1!r=<yW&`jJ)S9p zxt~X>Rq@Lxe;yag5ya%?Ih%NNuiFiGz1r%LpFWu4LRst%x*>+2s5SqH*ISc;l!ooY zn%?jckE7+4bck0P?^wP@Qjhlq+Pt^B>t-WAco@*YHPGgC^*bltMrUTgDTb5TjPYbM z#W@*qaP#B&A_tnTGGIY=tEWgeGJMN(KezqcG6V@%oS?TwRQ<}W%T??7VM7Q3dqmUa zsR&93?Sea><4O~b*g(a;Ab?h2rNidl2O$x+^~(1H`-~+jFlEms$(9isFaNF6lQ_D= z;YN>gr>vlFX@;`ZhQ-{pE9e;{PACyz%d<@hV&qL}DXcrmn=#{-%>j5@?WY#^7BkD% z{+I4&?GzPyQwOXUIXG)y&UzE9&z*8*<YIYv7uuq#?i&Y_#tXH&4Pe&LozvP2wS*G9 zk#Uw2ptiTBd%mvg5^-3WQ>s%(%JYjICdr1e;oMpWE9zqr;#zXVB&Z=|afY*n-4u9f zJ`3(Xw=FDZC%t(Dnc(9s!Z@oM6xa4(6+C}R*;H<!1=+hGOufo&phyC7<4X5(KhgTs zZKl6M|3+Z6R9P{<*rjA8=QcmOmu>rQxLm33i>OAuO+t=Uv1WOa3LU6$Up37$dYRXG znR5enRxDFSdJc3T+k-4(ND)SyWq5Xw@vxgbWKxVC<`YexX4U7ggp3CsV^Vf48T3R- zDU<W9tWv#1AUf^D<NJ^SNf14>-0dQ<-W57>bU&gpy%MItJKf^cs50ii<#w$zsY@ew z+@F*LJoqfbfrd1V)kZb%2j5zK$=f4a8vfdC){#2d6N5ATjZb@4a9lh*^}SGQr#!fc z*lMlZc}3$KK5}Pz#auF#gsd@>`h}7Parotr?pf_fJOPD-Lo}htduE!B15|!o5fjV$ zWPXA}ZXjnKV>((Wev!|cf1@^{w#DjyFxrTDuP=^Ly3Faa`i9N)9I4W&n2@8F8*+?w zfz(&ynbSSQ8FTL4DpqjX)Q^lssidR^b~KZc<)4Xt?#3z{Q+>p$Kn0uexi3W|0rD%f z8D&nVx|cYECmO_xKx8)i^+y3zIOFqC-^*dzS+Ty9EU4qWsBG02skc4REH2nP1S7EK zDTT5omXPEQH<m;qry7*>CA;G`CVE7}C3+pk0Q_RglPu-=pYs0lu+enoZWkW=Xpw#R z<HOpbLAFV~$C&<yuF$yFtuc=IEJpz%(Q=Q=SgiaGW;g?9z%Lz5+ZwU+44qCw#i`rB zX|}3!SqpM8<gW_BhEi%(z|kdLM2RP?q+n69vdYX895QLkBWqC3WCWmEtBaAnRo!>< z9U>wmP7>LQrPfSAA)ehZQPSkIHB7Q6u!19QL#K6toS<_=q?l_(Z41nY$fZQUZ!*S@ z8FeLcZP(&+*iiCiag$!~%PC67Ngu+My6+gqY%n34%|Zcat@ZkRUpM|PPu@u3F}lYk z#;Bt48>Blon8w0xd@*|YPIW$P!?jm;p3J$aKlZP@UCy0TH7a)`861s&pVa}(Ps5+y zWOPSlnXB)>0v(qfKWT>Toqa3nylz#OaEQG8{_wdpXj10BMi8!#{BAy#O6tkc<`R z_!nzKx>1>5y0%kapt}g=4i4Imml;i<>^Hj!Y@h-aIrwwxB?vS)@%OCy)B@3#jXco; zzDabhG8QI3*O0T^moJ&Rqc{W_PnI@P0u*n8SN!h#74L;o<&!&qSfQ2yAjOIgRGgMT z%cakCIU4pZ#pWNubb5<c+;&z1x0kmZ{3G_4gC6HU%?6qZ?W~wwud6F$`xe$AEQUmf znL+r`i`cJ>hS#K+8jT3|Nt6@P^qMm9*v{~Hg+zm5e$uKdPh(0+)CMUdgg&KQS(w;O zYq&aC4Insy(8SVZBH@xw&5Ngch;HpmfH^@Kz~(Z0m&;bMxZz6437gNOR5Xg1=OQ;( zp6YrTl|lZU7>$9?yvp{d$tL^Q6R%@&@O;RPV^pX7Oc;1jq>^||jU@b+?eQgp8r3%M z=CuY0ujut>VDoz1c6UBAB0q$Y#norrkPyRj8AoCHG@R#@8*M%~S!c~vKL2Te4q_Yi ztexYm5D*VpwD}$D4lRW0qpObF##$`fXy<L;vg)4Z@G|>eyy$BYZTEkUMeY7=pG)_3 z7wtkb{?AmN5F{b_)jm+f!4C0(Tf6(Iq`>{QE4?Pp+3zUu6Z2am7QSO+eTVTZapHDJ zs0)^(Eg6Qj2pRr_lSR?8pO}vKSHD_a59p*WJqtHIXaD`0INo_Xhf)=k?{u-%e(YN> zc?MG|hv~cmn*(H{V}m%@bEV8)c5J&s@`x=6Y=m;=jcb&l>3)~)Vj&kBOP?fO?_i<W zoq>qj(=@SxDRc1M&sIA1rXA5E3oD;#d_BuzH@Tl<uX=7Mzkw@nWcPT8$BmU;jw3Yj z7|}80)7=deWL%8*w8hr6qp#5IXJ@$oj(Do{Vjbe$(v|Q)tw(EoSz!-FZ;O0OQzwtz zDEYPd8v9)*@4-W;#me`1GzdmS3^2CqOsyiwT=vIse8n0=#)KrTgWVntTZ%-koXv8m zuSuinGoA9v+}Z}3_;_mds8V>489ufgqO6YQMV;<1M*6l`d)C7EK8Nu)Y+O6Ha~zC? zupgsIRSr9Ea<HMUNy*hlQ`%Q|ZZ^T29StTZ*L_mYd9?u)(vh5c$SpzUFGTi#$1o%% zT%T?K#6vDE$2+l(uXoWw2J4gHM$2D^Fm=E75MoSv>0D!jF2jQ(sGiDg{q-ON!++Q> zQ#O@p{E&B{;>vn?K1iSeo`O=_vUE9&4MxsTBgJAYg<93s3twh24f{;D_-1mbb^6CZ zhW4GYxF@ZSplER1aGafEdv}WMNBK+q;?7cTt_sgoQ_h%>vH176Vi|+fv(5qonZlw$ z&JDu@;o4330Q<ZaEZgY%Ee?%eqrpR0Y`Xg{x!xE`<eKZ2REekIoFknWdn*hJwB^?Z zDUXml9Cat~pw8SMK%M!wiFq}xG#3psFvv7<B9E|G1@Gr8yi+fnO}kfLRA#qL+$00Y zf13XJB`?%y`c-x|N$YA;Btf~bW~>o>l{M45B;ve=^x^`ifyUkWiEu|Be22pB#3=4Q zdg8_v>&wBXP0C_@Ic7qQ?pl1BDzm+SW#@_jTx91IW!B8WAupUBiUow$)Q3i}J;qzV ztwAw~L+Oz75R0OI+l}8~jWS1db=`8kw>^gu`BmRih%@6|oQ7cA&`8(W80|ura5Gj_ zTiYo^C=ZSMa5p}&h#4-c9B1M(*?yTQ*2(fzmSwcOgYi8PVBg!=`CwXH>3HGLfPjQr z726K!Te=$AI#D^YI*Sg>s67K}#!A2CM_11#S$6Q9a)#x?n8nafO2ts`r#)LwGaECL zpqp{{d1j_^sP%T8m8l`;naS?exa(<$SM#ifTaF_~dp^>bxs{oB$o?4pg4-{}yaOeX z>sdwl*3(Vhckh#GI95QpR`NY*MK$`9=aZ3|FEQy1GO3sq4NVtnDQ*u8Q7?8AM(nht zk<|hLUZ^%1&L%0!v_cix)L4F1><~<bhn_pj7>z3Gy(H0#3>n{m1uDSnO=^r;36*Fn zDhVL!>FR6s7cFAE3}~m(c4WP@Ag0PvqX|fb@t(|jC^|-lQ=GxUoM++kGfzfjxZlYX z#KdQ^KUkU8PP>|6U*jeuSP~zV_QP~P;nO<}laCxof{3}I?`#sv;%KZ{(spjQB!)Y3 z>n`MLrOW&2+R)WwDrRV@X2_yN!y>7>2CF-ZWbi2z<&_rvQ3t#qJB83TJ|>hOU-j>4 zI>Q|gdN<z14I~o6(Kd|Pb8UA6-Qm#1c$UeU$9<^C$jFp+S?>qpnaGwFx>0(erP#!b z4i=`P#RIm_)pTDp4~q41BU4KMsDPH}aw77@XVayGD;_ej2B-{I=d*=ft^QuIq@qM( z6MAuXw!uwg+JQ#2E~}VNv|280dff1a$oD#2pjncT&Nd^hOw=+nbGuKN!~^W2#G&+T ziw-sKQXd_DJ@FH&E#1Z_TBvee!|mWi?nfr<;IBG+cLA68vs07Ev=u*G^EwdVTJ1Kk zJ~74Z<BI1w&kj4PezI-oz(}W7L#kyf(hQ|hXJ@<$lneD9p^*I%KfS<SSG{bx7KM2s zzRjV6x$~a8#*={|yLnd2h0QK(o~4FBz=hl}NU%>Tln~vKpD0ugNj4*Woz;_CsV*)S z${&X>Y+R~u9fVb2)y%hjYGNMKgA<*91ZnF-$4lu{wbsaQ@s2K#ODJq9a!GYbmZ6h_ zr73)#!emZbe?qZfpP_akHU%3+{BbLqN;NQs-aoV%0AYVxo~_~~6=0%E$A2<STnPDf z8)%A7I8KxH)S?Ql?03|_QHxlJm#)V|o|?78cEC`Ia&5~=&~fktOeqdh>!HTWS@n4A z8(qTZ*?w+;%Feb5Iai6{n{z<;$s641CYBq($mZQ3&RbZM5F;C6sLO3Kei7w-M|?G( zudc0;w>@wflz812*c39O9c0<7?XXdRX^cerEQ?V`A&+^@xp;9)47C)aU5j{~y^_^< zSh(`D(eW~tJF2*^57s74BmChYt;%zKdYm8}L$$&);edVLXJ}|huXHc*6BhO=Jsx6> zO#vyaE;ud-fpi6_op=HtHj(u?v4_i~#dWxXVjy%Wu8{{zInh5D+F<=Klh9c47O{}w zd%fecg6UjuKkcKRs8!};t<<DXa9T~`gwtgrdWhsH(Wm%~1a*kCQlz|_6ul-ckPnMB zpY1u(Q_E!EOxD>YLeyvc9HdLi(yYa{{s~C7$XG(|KM<HXIPdJuV|$)fEQVW?KcA@M z&}}E&N;+%h;Pnl2+dn<VS2`>vrA`H9Bb)lBIaDqa7@d0X6XKU^496oxvvp+z4S{>^ zY@!7yJ#Q}dvhVZ!oN<afVRFd=P|5^UmQ*}*Q8-#iSvpTg8FTVgkIMDWSPafFw$sP? z(@Oo_z9v5egNN`7tV+Aq%1I6s`lv-~lhTm9h_kgAiMMGM>|0d=LXQJ=H7TDeWvU!Q zgOi=#a=vzpEu0!t>a=Al$XBktc-vv~)vYQBd-g!F^>@e8$2|(Mr(z-ZI-$^p)-^(8 zM9T3|4%e1xqA+_USVH?>uT1?a<n4S$od%pN+NX>MhH9xhRnF95&vrd7`qW%j=i|39 zMp3W1jEk}E8V^S^v?heONRrX?-Az>HBjRMXSqt1~<*&!>5{|Cgrk~SZwno2%Lqsb5 zgh_}|zR^<X)^h6=E~Zd#gg?yJUcT~u;>9P^Ukg_j4g_CQUz>80PE%_kfrQ-p>~ZF< z^bgi#u@&QTh*`NE_XudT?-d&(VNBU_iJseTbE!-M6&bMs*`cU>v#AOv)$uH?R<Gy9 z$G)~d%@rIfU_(UxcEtUjzf$mp!{BzSNO*^Lby{^W$tz01j5Z4G$<3u84fK~;%TkOL zzE@xgOdtg=CNv<3+N0~Y;W0+jLhz%>sA6Y_0Fj!JBOri_e|c{{1QN60FgtWCCL&2= zcKM8hX_lVGLR7u><|S;SHfvQ{4T-~*4-wXS@Iju9fQD;>F-k#8=}TNaO!{sdmsN*> zH!fT_!tJ9$7Ed&Q1XBzW^?`hLr0gU)j3SFUPh*R<&5r{JlrvP7M)t-Tx4!&ZD4~^6 z0BdInSENbLX@uTfOrQ^N6?cM7hqU&tLqo=|2tqAbDOqxLyT5%Kl{AUTBq5_X76x<1 zu2{DQ+444=a5B-TQhFL2ugV4E+T)If{lYPLiYV0AR@^W(W^(jfJR~=rsLkaG-|Cn4 zzGAnp%{rCO;cfeIhQ5K_uJ<tT=K0P~Gpa!V9<ip9X5fR&kqc~;83`H+9Ai>&%3GI- z&Dy18Wm+$@QRtMH*Z{Rw!wqNd{my8eyDGQqITF~{|Cp5?#ukIC@W5&N2tIFO#s^`9 zJsnMHn7C;svFX-2a-c(DLFYch{2rPmW}iZGI?ZIydQ#1n@?27Q<x4wPx(9{9h05$K zb*cmO{n0c!hAm0Nw-=v^p6EPlrqObeem;WJj8<-y&R9xKd{C)Ro}n_==`1hnw(Og} z9%b5$;}VmOMyVJwop!M=#n@dTXHHXVvVuqS+o?j{mT2$LfTQW6m$P#Mvbx+Kl9;$Q z5|l-$L&N=F?PUyL(&ZYcv}Hufj!?iDfpGp$)-75@OXPl2^T*SaGo!}Yk;gen=(I89 zG)qG;@C1n4Vgw!vuVbYukmPNEmtc?+$dtwldRD@N31Yfa>Bn!3WR`Ij!26s>Gsf_) z{d(csZIqLA1)rPZMU5t_v1fSvC9h9++wfC&XG`f+UWb4vTB`g}Rq641c%!k^-e+F0 zp&0PO;8u-Id_7fdFNs1w=SQh}(@Q*NMsM)}C~!M*_%?s18-=}M_I`f-P6sl*<$UeX zg3WtR_|oF8gJbhN`EOqxQ@V2BNdOcDyh&ptCSmibTA<>6Ar!$6S4Cl&ZxMdX?2XzB zJmO|?w2~OTw2fwq+1}vGHm2R#$><!xc5E^{#W6z)CuMjV_t2jZnapYp)*pV?1R$0c z82Vce^W2qJ-wsPx@bFqxmRAczRQ6weUe`+yb;m1OUv>}F*52s}ookeHYwx0$X=bb* zk-FRFsBf?acNT0eo;{(sZ+^1nfYSj!U#ukSLOR3XX|nJEn=fX$(HRFW2TK@po$nHy zu#pyk&p-LmG<}0>7Tez}U8Z@5=GG1i%JYtQFUs^hpl7w--GCZkAMhCuU5jazIF>{) zgkN6Q1x!tN_cmRAt<m#=bK`r7bXZ(IuQ?NUZyd3RNG5SXTgg$$(47715)T8);_>!{ z!%;tD)iJAw!SC&+A<uH~wVmr0ZWa4tp9M55-@!*YejZq$0LBs{mKJgD5<QjRDb-IE zTwyrtCuf!9Zq6@3p~GLnlq5xH(G&+@S!d5agqRnzWIBuf;Jrmm%7*c$<C93w58zhV z2)?L=hpy`a{oZ}ztuJE2cLi@3`3$1DcFRNEnju8qM{E76wH*aI-!kSVzV<dz`_o|` zk-&9DgT+r&3lC{Y-5L0AYK&Y}FT*1-c{?XZ;p$D6)Q_;rk9W-nwx?COb&0P&pT|(r z+*c%kub&&LY<292Eqd1>imxUxFwsC%?86v5zqjyF)b-^hM>{QaZ4)yXY9^Smc<#RK z`1otW^UY)ppU<@^>T1rGVy-LrPANl$Z~i+*kySYuUERGBenxM5iK?)_jT#c;NxRIr ze#e_^v)*lXRry`}-eP6W;pJ=UpAL3;9Y9vD2(2`gs%hMZG0Dxj?PiBa1q9E0vy9l{ zA*wv@q>2jqTvuRh?V^c1^AmG(Pr%>&NI6?_`&Pv*=}GGK304iujRuNN{KZ+!+wvPR zNF>M*bk;2X`pw?m1ljmSa3e$pjwiPQ;M#js0Y#n~0|oGATQp_?_}8ywta9*MLKWiQ zzlwP^O6ca7d~QBM_}6aPGKiZ92fE=GzPHC!uJ>MhgY;XphyZ<kx=EO$a!{!^@~i<h zsL|yJ558$wzkQB}*q*?;jl~Q6N%x}WX*Z+}`NuE~Vd@H<O5<FDyC1v(QN~9H5F+zU zKF(`I9j31YJa{LV1w3OX-eAJp+YOgH#%H}cTa({y3L5)B07SrIvOnRr@#O_fh+d$& zbC|*J<rdoHPa!dDmkoIrsy{0nfe?E;GCgbEDTJM+!19wt%zpTSXB7Sg(A0i@siWJ$ zvXmNPXdhNh9Jr>j!vyrHzV7QqUAZlptEHZ0F8p#ON$}RKl45zCsNH=G+qXm$^&C;u z60R}5(6ss;2-IGBZ3;vY*o-~nv}fxD2hezS(w18$#AEOH!y&ysu@uqO7ZY)II}Ikm zDo`q(KJ#X9xpQ3nK!?o9E~W3S-Ck36=G+e0Rn48SYde@h+qJ0(kNnITF7xx&F%*|& z<uL7>I0%d9-O`Q-+Lt8nWS<bdGi5wVbV~jMFl~|cZNR{dnCDQ9&_MTus{0`umBiF> zjDF)zU?|mt^3x*yd}y@d47`&`Yffo^K<^9_r^@OCzW(a+TI2gr*b#vl(wliuY74#S z<VQizKI837Cg@nA(U^q^n%nKhNHZ*Ns>klhJR^O*D!ul4P<8e7PwZ$02kF)-FO}KS zBZT)GWFV*8U!KwGZeGh92_wd>bulT9Un+SDz&um#bO_dMDltt>)a#g-cgJye-LLt5 zy+ZLLE}RT=2DE=C<}M~KT*Bo75;V}!XXggA=PqnwHay29+QYiBTDe(jg42w<&bO@4 z-i6snwxD!%^LpZ1y@JWS>+9F2)zDrHb(!Fr-(Q<f@+uJNTW=<pj=HaQB(ky@9Veb> zuKV;Qm8k<KalZMJ%K@L@X7Zr$UHV(!?LjFC81nRYf|ThL(Z2IMI3EuhRY#CI7Gd~j zj1E8o$qR)J4nYpG<!2P0{VY5#C*<x~9G00w#{P6AB~<Z+$UNrTO14M#S9p=qa0SJS zKd<tMbUX}C^{-)^%UpKTUG2cDqUEHB%io(+V6<t#Rk20PYCn)uv7#4`*U-C`muU_} zI@}32i#T+oq02p}*RZf^gb2h<vATA@1B!@u3IO(f+QaJaQwU3N7L!>6rfnyzwx@6( z>x`9};Q%ELNqr;j<uZaS(n#0T)VtYJ?wfsBvKy)wvbwr{y_)9e+{j)AN2`KrM5en5 zMNF7WX>`SOpF`+}8>skRv+};4NHnK026KcF>R<UQ3*Iy}ZnSu$2`l<knTdyZvJ<ZZ zqr?T%e>jzpue)&j9Zj5W-dIh<Xv#`|X*fj;%RUXS4L|>0AZGurKw#@|<%fyHT$>dU z%o_D}jd=t$X$I4!sETv^rge#w8fgl_&cuF76`00Es|fJ2mTQm5_8tTGsCR%pGLQdS z3xs3Z&CEXM?(-7RTfTQ1QWFmX+Jc)mw2zCu2GNuz=iZ*ZMOa)mgg*_rcRPQ(eHs<c z4AK;~RBYn4*&t&`s7ya%%c_|8xw<0=_TUv-I%vP>$iv^qG4KzMBx<@O6#~qZdZj1l zxtx73m8U$|lly~qCZrh;`!J6kDP8jNbOrS@$K*69E7C13?kfY;Z`Ifo&kOuU{-dmH zqi12(ov>Lhop<@DVHp%&;iw_;_CjR_QRizdgt3v}UlJ)t>QWWKR^I?I;-#pfaR1~i z3wL!rik~X2GBFEH`@svmQ&Nmzyguo!rg8Me@*yUEAvzsN{;ecI)rVR0+8XyOUgDZ% zbV<)f32{#HN+3c$1wx!Qt=A5gsfJMdBit0|iGIBnS5kL9-PLtDZ^7wN74x&1wjI6V zj8*Ed0l#nGDhtoi2AWxi8oWdA(AX)0T#VyaX_Tjqd$dj@VLanSlky7Vz>>CppLmCd z4%0Lp^_tNR^MZ}^rhrG65T~re3U%M&i1#VB1XRc9Ryx#pctWzJ?hx2^9ld>rq?u~n zEkU+?ycStfTmyzhCMpzg0=sU@gWSCJ1=TcBn_D?ae9+EP7R6k=9o=GHv^K?8yaRPH zQS@FjY?*OcgVcj~EOxX_#!n7&!Pk>EOYWnn{+Gk^xT48*ZhH?rd$J{q`BOE3-A{KR zz|h_A-PVrc`9w&GArq1OFWro4w}~Q-O@~U+8qQKkR842fXVJx1@t=GxKdrbMBf0vE zXw=JGYsd|&wzq7(za`?=!;Atap3<19O>MW>-V?kvw6H^48crd(xQdqzY9e~pnfySs zLqSyyiMsi=<Y07oUYVNpGQ{4AA4J2T)iQD6Ro;p7U}roh^0#EnHvH8`aGOpmQ?J`6 zL3@EW%&197O+hTms9Y=VEz%QO8b$TZhA$5emd)PR9R5o1Pt_$QDR*j`FRpT!=p<8_ zNc|$B=00wDWA+xOshy`i9_N*>TsXFKGurS6Pws)tK%xLQXE+v~Ba_x#Fx3=@$=l*) z=OY=`KA3R?3uM#+k9OFOO}iY%K%nh<^{g2iG$_@KtvIf2O;S1g)m<6rao>CFMnPmR zH>9eNMhr3##=F_ANYK!qx(Z6kA=Po+Uo2p~Jnlbyw$56fxkmG0@%E0#GJ&_SZ1v5` z?9o|z&5GDY;r-#<=c|0w?VbDcS+V59PFp07+Qhgb_mo?E(i<lyVvslP&s6+nGx1OS zD&=_23c9gnU1Sgh9LWXy9ee3Uy?BR=zAENazE->e_=9r&#hP8PtPn#=Dv{k}saSwp zA3~IG)p!r7UMApF!f?2~c26X9<ghWhPbch}@cLO3F!Le0X(*iU)BNv_`dl(u?$*Mm zg!~Le+AC&42=XAr76~6W<u87s)0inct$FXq)HywCoQ1>j%&!(ybutGXJa87y_&{#& zn^8XOO~pptp4_|SC(woAu3WZq4}Dl`Q*}C2emi;sw#SbKzZU{2sH4T%HAX@wHKXyL zMm;C4*-+8vZ~_qyETivUe=vuX7jxlcwRCB8MpKEw-%C+MTu1uKxOWD<48toc8H*!x zTzwX9bZ$?|BdfkU1F*zMcskqcnwWbiC3GyAlhC?zrAY;Yf|!5*m@Vs&>yq%yKCObG z>C^JWliM|1-hdVw+M2@vNFBL}?zsiSUT-Vt!*{1ID@~HH8yIIXSv4zFGP~mLV-&Qn z?m|<NZtZ_sH9*BQt*)lgSBG{GJn3F!z|{=l^L{q|Em|XGxiyiq3&z#O!E7W&D$p}@ z9rn{YCYbNAM(z1+84U|&Tq=blN?v^BB@bH0aQ;$1QXJJpZJ2{TA3&!c)|@RcBePJ_ zH~0#*Gap$+N2&4l>b30&W-LPvU%)`jg8bUYo0s89_|?)6a^kZRpBa#Fa`_$KLO4U1 zYmZ|aD#p29_W3i{U0*wB_=sJ&CwGL`@U}gUn~uFaMp%&GK<dCVN5E+EY+JjViY>6C z?^RP?h|x7*2djug>lElJdyfr^1=(1Xiol0T<sU<%Rp<!7Bi+i8j=!Dxa7FK<-n@Ek z6%hUUJ^crN>rkP^%~vw0O_#%G`6KFCBJH8_uh)mko__4+zHS>)2G9E)sJJQ}NMjPU zYgxDQZAZfI4<k-A6dg{FIb4@}kD+K<)CYSY7g`?1<l>*1nyh*JD4lk;s5+H|DsbFt z{viG`qP_WEc5AvlHY5l6gUJ47uo%a#oNYN9XKVnRgcqDf!V{N<4Wc?B(yum9{MT7m z&00U6@K*Q4+jH3jzWq%8BI3!zM=4UU^7f4pn`)^|!t2N129L^+|BgloV!jwWQbA{r zB7KVS=HbJwQ}y@_BbW3k_dIorp#r%e%%4wZ5BuXc7-e$7PEEiu3wn4I;Iph+{G-wa zUWcH=wZVkOI+{#;_wg(_2DlPAB?Za}NY+)4))&3&lj8F@YSOsS5Kb;>dOCjLx2TbS zo-{(x5Kik>;YU(i4?BIURxNcHQh3y#kqWq{#ad$+W4*%P|Nc+?RHoQ;?1xe9hd(zr z74{0Nem9vw`DcED1sG{7=Krro-*D)*__p~P^(UDu8zs_BiO12z#Z!{~y|F}^BwYR; zyzSoJUZ7|zUJQZ>jcfG(J|0zHs!YuqMe>)h-YO)5oio&IeKvn*Gx`i5b6xIp!>@m6 znWl^|cvK}RU7qxpfc8Sb<mmIg6aFj@hA}3LT|<$=2rb3m$!en=ZaAHUty#y|Dv2}w zT|<nnXs?0=o9Le3H`Ue&GQn{thP_GY$5#zkQTp<ls_ya|$2gCyaQQA`J+AxA^3d-C zN!)973oT8}#1@c(M6fJ(ob|sIM1)zptY#I6lhTZQnSw6~?WUSzQPHrZJ7ki6ydmW! z3FMA$4s`9>p(#dpz8k4H!p+me>GSjP5{q$9plDF}N)c%S%X@ylv2X$D0tfX3qQ|Fz zgIf$(49_PtU3P*W7!waoy;Q91=|26-S`u&GJQ0BNRN9k!)mgvT;6BE&LZMt=_%ZyE z^<ZEQMGnQsP}r;+f>H6)ns9DJ{+E*v+b0h&|K9uHr^w8hEaAxL<qHCfVh2AKzQ50J z0385+eLRTaW$pB^$S6;0NRfl{e;yI%zPww($4t1zWG%ld&il-@#w9ub%}Cj?_;e5< zX>~`L&zSlIBvVD*;ptex^O0Jg5ZgauDv_3j@G*HlPsj5>%D%0bhWhA(=##pwg<<HY zH9nojOTWTZ`QHvHPu)lJr3MOg+{`}8J74)cPOXgm2?X|@CAZPhj(P6X(uBWMj7HVI zXt(riIyir%*hP$v`-=tJXSsXhC%lq>WfUd_!Hv)2r^|{Dc=Y#8QJ(7OBs!lM+#S0$ z>ZJ);NKM1a^WXvso$Enb5~|MHw~w;E09T^K1Vx6TKxK=B*F^eKY#PQ7(@jg$Z^v2( znpQrLjs4RXR8KE)FYzyvHT-|@=)Cr*mX1{@uk2IG&~bvq(#Es@Wy&ZFFHi4OZ%Cef zE@CGl0=xx}@Bn>cF_FtUAqvk#oid~Q1Ko`=fGSUakT-B>UVt1&7&hY*gHH=Y+u86S z6{jct|5Qy3h8MB7fHx=)J14lTV(TLGCBuqXbg`l*`wPX4ktWqm#r-V~h*EXPwIE&f zq?>8XlG^~sPYtJUe~9?T|H=G_`%xd$b|@-@`BmO>%GUL%W>-)_Q=MG{<uCHv+Uyx3 zPu0fjW~WN%cagDS#DRk6RjT(i7+r|4e`j<1>KAjH8ajHgeqqTapiQr(-uCM_*he?5 zO;@yR5`p=HV)9Dz;awWa`(k;iwZjPV8H(VM6Fx+}%QLwm+TP-5QebGf3Edv455QwT z&@w&z<zf2ip5Ii4kFddrKIa79<oO>q^j3M}Wv$`%PzltYb9)#=;&%ANj6ol_tI{7; zzR_?;>Gr0x5LI6P9_zr#Xe`B2mZ8VCmUA9>y$6=}&1TPwZ65{?xzy0)!ltV`3O0F> z)a-!Q-{E^S({al}9^`P48*XrFJ1-8eLwxs$Py0`$Qkj4g+Bvbjo$1<wX`PMoYlIsd zm#Hcr9X)UFm!1xX$o<O1$BeoyaAWf+Py<9&_olVFM1c^S$?*FfI!_1uU;HQ7?}pE2 zIIvaSB^nd<$UgFfNo6^__t0(8hOBUke+al0k47rCrnilE>v<baEWQ@q`6$(1aMbSr z8^Xk$0_=JJto7Qfv=zEk+=0J}?u~cQgJ}v0qxh>PBLuGizqiMzgXQr8X?lPr#LQR; z{HLb)0X=%0aD?=zlXByq5L&ixI*~pqnP}NJ0dccYEx;R~r0u4^M>efR7>kRGD~x|k zI<V6|s}#Q5ty=1CS1!i9oC=+Y2`(VGnzBQ@IqL#r@aWxQZI8|ubY2&cd)8LO#A(Gj zozhYG>E8LipBRrRPZ}fiIIk_<s_VY$O5OZ5&Ze!(HDt~bp?^NEoNCGS8}@{PysoM^ zX|2%ouzV!96Z!)2OM*b6nC`;EZ32EEeyJ6IBtil}ggyeMSO16r1564AasQu*TnA{t zBHsI`A0*NQ0psxNtLq~PBDjE~d(m0`S#Zif#^G63plh*Bpd|G-JLw7K;;*#iDa${K z3$*m~FyGf@?@+4>VPVKx14Hyor7uafN9<z?-XI3%)Br<q@oLW^ALgYT;n$|91q?}j zEc_}lVW+)D-6gzC^)U4Ior+MVSzVY7m-7K7J&nnc&3s4#ixPh-ho=p#Db4kpM6AQI zimpzeZBUPMZ)ybP{JW#_l`e36BD4(mcWEx&m8d*b0|y?VCg*e)l$(dg=c|P4(iwxG zo&#R3$KLmvam#-Gy!sn+bMu#;AB2_-63KUX!mf6R<d4Y&N;Rh(!X=!II81NK)qgjA z=^-2B+Y(zdiqok1Js#HMvOocu-7OzTi3iOlkTje2kSMBUmrZZg#a3e;8<jh*EmKdt zgvyD%DAv+iON%=vu|u43Syc@rEcHONH$52Bzg77z;5p#5H(D$ExIpA+AArp(1|sS| zwqysEq<l%T=y47HZ-;;VF)%S_EsB7F5zN~OA}qF%+pV*~Ji%5H8Iq~KG?9)etZBVY zR(@82I(<->Ef+|*G-@%$Fay#)DR5h(ZWN5s$uU(K8NMl3Dg17ZhCUtXH0A&E_V)8# zUEgpq*;Jf)l1(7+N2@x#+@n4q0-E13Q1uM>`GPspihYOUX_$yo2eigU8K{CaFnImC zpq>U_xojEb$`5a}F!g~_ZlmC2#Rn#chrrB9?tX*5gzh#Pns~Z(m`zY$Jo-j&^a1(A zh0S((l*bZ4oTpi9{?QTlfzfdo$Nsu_5nj?>VH4TSe+&b_`&|#h+u@a$^2q65fNQZM z?>`<a*zwolJbs}<+R`)c<Vj5xO$N_ra{VT%#s={O{(WG2J+y^pzorWIbI_Tl7F$KQ z9+BE93e7@HYnJ>BXfn*0IK3*(Gj;^!hc^h?t;|H@7)?sU|GkPdxUHw^a+leI$<yFh z5SslWwz9Z<=a)SU22GygLiYfu=$4d}>aqcTR_hkd$%ZeLO0M7Kl)3b|0e%k!-A~{< zrElVwy_&Lfv)*tTRX@`Lbrmt8(g$MNoXL(`QoSOu2AB>$dCuH(kfixRQEV2e+yUc> zWw;W_)0z`5wlM{Uw<-!K{414t)~S#4X9jd-BGadQibv5-d_oA8_PO~d_e_A?hdfo} zdsKCk085(V%|0IN9Rg6^5Gj89abbP|*D@Pf<bOQ)^GAT6^?xb-TNrllS>G40b{F2> zs5ziLWk{rWwqPSskdZ+~_2Jb9kG86NQG`Ifd5w*AjM*~}hZTEZI^rJeb1fcx#|3R+ zY7jr}%8Tt%51QYg;>N}~e(+~vGh@=sBHm?98Riz~i@@R{8Y8X(9_X&^gQ_`+6d3X@ zGOH`L`8i|wrgOKbfPcZ}JN&w=gq^uISC9JGqEU`~_|+?{NoG&>(3U0|Y}MLTi!UVF z|5W0W7fx>kJ9nOvDnGg?UNpcaMpYr2?2%NcaLWRSn?hX5(c+(?q}hS@0*6u`WQJsN z%v!aEuqx8Ms6cr-DPJw1yYXV;IsTV4BG+9e+3DPD9)O7TA_FJ2R&|%V$_iHQ|D(0H zfQmAF|GWWd1Qd{vR7$$L1wpz&T9lCPhM@#RI)@JF8c@2C?nb(M=&k|w!T9Z-v%Bw} zclYl(p5x;@4D;M`*Y&+V-|Ke8CQXnxQ{4SBsuSLnf8rL3_?9BAB`ZQ$8&4~|@v6cg zJGt-3-aze?v(NcoEdU}efFIv@-fBAf=c_7_YDr&}-_L*6UDx|;@UVOnn21x-b)Qu{ z%(j}tyQV`AT|%Um{?o&R49S=tN_%J<kiGiw?>SqnJYDEv;K9}kGdh4<YyC__Jbo`c zJ?dg154R!>fX;!58rb{~OX=>9uB<l@ahN6;s!V2%a`J6;^o^Qr=XLO?+(k|f4l4B6 zuW9tkf;wEM?!LNpvzW0Q3s}p7MvRhz$z{5CWbK3|3qg%o*B^1jW+V`RIfE_G(tE<= zPU)JH8HZ)oGWeNAsSBvzz|Je~CIr??t-)w;0E^UFW21Z9U>RfN)iog?l77{fQ}Q+- z?HNvS6$$;bJ~a&iy~$&fD3PsG^##V`&2my!ZwjU7=F@lYn&T#eEE;QjU(hy{dJp&P zV7$Wfv+%l%Ke-64R>J@9UwHeIjMXsswzhx0{~>^4%vbpn5y7GTzrJj%4IHX;5r^2w zsi6-9{R}=!UU}I|^eChd22C#2JM-^9<T#E#-Y`@NPZ94_Cj`S*&fdJmt7;G)`VjTq z`(X;+3pwLg7Yhrv>*M}{x(Bp{_{bfI-FyC{G-!=LOmtVH>gh`m*7sYd2+FGoE)j_C zvqG}TXEL_WHRI8Kj)_?GU=R8*oZw9cKRmrSv*#?zPIZm-QBjJ!zu?{@JoHQwknk*d z`Vl_V=YPNS1DmO`rGF8YkpeKt9opr7$IHM53LWU&7>qT*u47mfF;_l0x9R@wrml~T zX4exqo7@S^Vv-L+RRij@+{XIy9f>Xl5?d=7c%7*q4sPPeGG<mSxLaT+)vI})4DdiV zl5I3p^Aywt_G_}VZGR%kw*zNt7hBiwaZi++DpvL}*XRHANAZv?SI28S9hHSvPU@@P z>{S~`N7*zSNv<LqaTgu9Gf9`ppe)S|hcAF`tj?B$$TjHHtBr=!qN-`rJyl8?&u6S@ zl?!toVq-NYDS~t0SEzaHewR{S`xe3soj4M{-pYbTies^vExo>q@P@VR-k+6vs}mS_ zexD8+3pg=;711)GX;1=bIPS&i3UQyeC~f>2dj*+%xvma7I&8x(sy}GbxjnCLF8NSs zEnV`x^SR{VusF6lOVi~yKoB&iLpWm49MG;6*J%1lYNGN=3d?=;RE);XIFY8GyjQm} zu80xT=y9QeOCzVWMM`Sh0uQ~rIndlGZ3ZdpXM;p;PgT#RK1L)i90BK`7uiPjtfODp zurwU{xdVqJkDqRj4b0z7eg8OclTvfCXkJt@E_5-M#ay$5-~=4?R|EtTND2gYThuLD zf=BJR&^S$fW#BDiv@J?1L(}*+Q*d3!w??uiOw+w`@%3E|&*mJ43iKP@iA;qibsZ8H z_-7`7Q{?5y7QHU?#$pzXV&uoaGC)2ZzhF4=yt~?jh70T~8F(HA1pAY%nf6eF)ruf> zg=QWX^DgOrSX+{Jnc@i6WRb$BR#np`=#2_6tM487=5gv8PrtusfVJ-3t%D6Tu!S!( zh|olz6Mk#I@p~Vl?AsGVTQqCaJbwyR+6d#GdCOGRf*{GV`*A#9844Vs%H!62K4URp zcWTYEXz9T99s+L;yF7vdr`(tFZ)Nzu+)-yoMe!xmjvx~nci$<=i`TK{3uf2pF_n@E zDu}UZzU9u>T*Gy88k6TC(#K$`h}5?H-hnsM8~H-qkC=Cy7)pLt-ap!xKTtiTJHXaO zlH1U|Nf{ydf!Bv)7cSf>;F*R}vQ1NM6g<K5MRoJ-z}k*W^NrrH!U%UH&8U<>Y!%z5 z;fxH%e4cKv;~rY`r50c4rpSG5>j0`>1=HI0*`&T^vSnSc`$6LcUhmhcVD=Y20yD@7 zq9`+4$9*j16(Vz@WUfoT&}dm^w!J4#bYe5<vEt!UR$d18k&;Xh7U#w{akyP8yOfLu zvLML1XqxJdrly4Owj!~%J~lQ@?~*65Ij|o=Nu42V%$Y^K68EB>Kw9~+mPg964>_Lk zja_>LI}Mm<7ni)NXzRBKGG4pCyJ$w~qSmhpA_uvGX056*R}=;IDqv`|DNkUMB)u*R zZktG5Yq~;fRx7!3i7Gg>?AU^bqQj7?MbHgSZTTR7-xi#R*J|X!WT`>$V@Ia?-5<G3 zHf2ANsgGz=9ivBlJ)voCPed%I%HEUnr@2E5M1ca)g||6J!03xOzJ95nO?-WZ=|56x zV=lAuxeMqO)<%*0z;|u4_6@4ONU`pHf3pbQx@sf8dlLJCDZM+W$&ha3h1&=Z56#fA zPSwefi1!_Ev^(3G^T-<lq|Kl{SeMA%_pR%1={wK0%sAesjB`64OvNG@$5rg@l@AFg zy9ygsI&ize4dqw)R#_TMo`z*f?|h*QN9BE)t~FCyQJK$l9A+=kG2LYjL|o~qv>we` z$0$Q7gv0NY4=a{*u(of3!Ee^H;gz>j$Pxnk$vXNv{_1A&{?0TMmmth`SO^W5=R^CK zhW)3>7`gja2_L)2JS_DuC<dCEPI$tlE?SR?t!py*0x<-R8|R&8iMpzedl`tkCaY~0 z1<&n9BuNmUx)%v2yyRH+z~JNJU;6yS(Pj$P=~nY;$FyCNU@bfKwZ?`1@vESH;KXv< zC@E3lcIhosyDPSjooi>286&lW{5uKBs6vhwpe)_%+G=U33-bXs-R!51ikziE-2-gn zvpuxwDU*!_4g2c}#FCUow*(NLWu&wr!IMzB(!=L`_SrpoAQOwzEPY$tLXYgur>nW} zJOb|nV0*w+B)MNwz0kXQ;Eyfby|WN4O}gESsjk-8J1{;daA0X&)1rAX6DB+_fC*JU zMk8Hu&DPH$O|k1Fa^s!$zTGi^Cz$u=8AJ}paDoH93fWI3CYr112~fXK@jr2%GDK-3 z<-vRvGoW}e7)D}^wM$|3F(*!g-kf;p)$-M5`Vb|lkoHsCtBF#O@wX8ZCGcg56j6T@ zS?{2%M+@H%?_RpR*ov^cbdYv*&rYzxhG$uYzaG|&>v+?_jzLK&i?VU-$KR3LB#%gK z)7!MHD#(>=<7hqoF_Y%O(vNO~5=_e8qgJ}@V9lRMC<Gq`mi$8XSAhOy#>{ztHRO$e zk{={Cz;;VAnesB6;95xow%lqipf5B>sC)SaS?BcElT)gf4rGpRecC>AJZY-|zS*$y z%Aqb{zqB>uL6OTP|8>VziKor$@j6K#U#VW|USV#&glDuR+0F?l^Fn2*Cu!a3=NRhW zSss`Yq>$$|tS#Fz-Q3uqq{n{C(|aV-*xDmXPEvCiQ!%20v59+qdgAU7KF;9!^ZVCw zDd9C5gqy8QjOKhM_afA5yPzoXG)uiN<yXO~j~&2OcJoe?%XqlM+>r!`TP*_!)^NL? zEj^S=CfHrdw6@=-q_kb;oxsyw-?OWZfN5)bE9yQHYo>~H*M6!i>pTaPwX&8_B;1`T zX!cW`{Pc1#y`ni=kX!Z+9edNUgf&t$p+#L&&p2(-EYIlM?j+TrE_U-#Xfd#(Z(k)! zLYzN|o&Uy^2Hp;dc1x=sQxGUnRp9GrS4`nmwrV))G%+dPAIQ&%*X|Vrj}uINJBa}; z)y}ebU&-cpw3{Fhqz&Djq~CK6vDoQ9qzt!Rof+WEYxgLI5ZTzI&#Cnnjk>MN^gNF` z{JkbgmvcPwe$sKX%okyXukz9S5~^zRfq%4{ZhRoycz&?Bm=a=IeA3SmF+G_DXA-gr zHM{yuL1?JKl@=wQ-sD4HuN$pdfh#0v>DcG3JjBL!@_cA)rFA${Tpllt2#kB!|19RJ zB1_%<a;CyUs8BilYlhHn^Zi|Ju|0dQVKwF+1>VNfFY{kWs;S_BAk-4229sL>J*M!< zse=aBLBmOsm#*AX$I_p?61ujh!}+ZpPh;E;lOjR-7BqOvt%X6l1D~mdLW`LjPgNY4 z@7fS4V20nm?sFw!aM3s#Mn5JYXa2?h2#<hOj>-?W<1^`J43-9^RM(YYD6l@WHnTl_ ztnfU>Xu<p5%i(cMaue)Ww!HZoTEwshj|*lsLxm^MyDgsXKfS$ZzPCz&qX`ODpv=CX z^*Cr8kRazZm5cTJD7FwqZYwGZVpSy;6G>OA(==jkuai>eoPHc5#yWzGJIq=jhj8jg z(EAyKGbLpJ1K0obNr9h#`v4{l;qpp2`-fI+{pM&mjr_bZMUi>lUSQ`nCkOp$!IS`( zo9<0KlJ7pzHVy^$8(c9}k&5H~AYscjJO9tg<O1mluGvA+)Mn)!nCLW;hAO|(JoXD5 zS*By9KeKAdU%=#=>>gzX`3)d4j92e-5piEEc$5|}r1+m4ftYFBUntf{mp_^z^CLUQ z!dj(Kf<BGu;huLK`&Q*!v6lM;7sZKXR*nX-TGPNweS3r~C#WJ!SgvlY(YidMq1(bK zGO7xG8kmroJ`v{=m=W_TTkp2UjEAb+Ng+tjySLXm?|JpT*wY~H9WHs=q3{@zlGz@Y zzHcU}up$OJ49XPAE+W+h4RB3zcG;2)JwLM$69X**+g-u5qh2?5X=L%}8D*p{J2Imq zOWd265vIADDGmxFY$NbQZe(=Fgvage#8<cAW*BuTsh++8%>Uz;7|smYp?ogyW8Lv^ zaHa{~+r=k%+dd?Lm~43AnaTnN(ZZuYB`5`!+;ZFf%Sl^W$m;M?3C$&2hKn8}g<x4$ z`g08B=76G*1J$gE9;ZW-qq{x7k~mT>@zn^y3Q4W>1#EDG?pKcK#0LoRJB|28LP}!a z#4fpc@<v3CDa;B!s@iG$l|}kP`Kk5AF^P8C?aqRY^7V3+?{)i$Y5nboaNq14Qy!GZ zN)dz*Ns@E6>@crb>7lv^E8>J?3izQwJFH{FAX~oOn!~1Xo6DNpS#|QY$n=V(LTq=> z&C<BQBvp&n%9oyfl^;5|zZx#RF7?o(9$Hp1{e+V6B>>|k2}AE^G%4j5gSKpJZ!TW< zpG?WjK66}6Oy3{Dqqf?Kv40<evAbV?pidRD`Lkx)XAE`cshCbW35HE3N=)bqGh@Oh z<C9VG)i?B!+%$xSFNsX@WKd7q<dKfiK{&d{C|ccOA}Ol6qQXBPl~-j!)i3zvua{~E zxM$avr&{3|7hG4v8psk^1$%qa2e5Hrktl*iRsJEuv5!&?83mZyx7&8L>#e5jUlAV% zqWLVp?+aLMZEiaw4)3zj^okTu8}%y^-a1MZ0nSu9o@TsSRVud*d%XU%#X%MJ2I+Cw z2Uk@A)bf|lj1KqB_~6Au>u5hIjC8&mEbwJalV6$dt?^lYCluWIxa0#H@?*Z0R0V<e zyw%a0)m2!#tJ7w>e5`_nrq@ZCijOfVr^Mqg!(N;i$7><i2OI9!ZfaZ>aW37y*IK48 z7C#Ptq1T`p`hJpOA-zM1d&LQR`tiX|;)WqtV1S^kq!9@?8dn_UaGx{K=Lk{?ny14{ zhN(8+86zI%XN^9wCU)jKe1!}ze)*W{$r~Ub{E8l-E(b^OnwK3>P<E`eGhy@RGlu>x z3C1#?%?8xAAZ*P9qmvK=*vqW(m5G&UR(ujMxKdyX{e#(U`l`KLSFkVMw4_^r#%Oz4 z%i$3lFg^-ChVw~oYmI#ArFwt-EbacIZ<IU6wU9l>kQj~cgw9<;`V8(Taj-BQaZQXk zWkfx0yn|Pt7|Z@!V~w0_+=Xj}Np%DKD{)1Z9DX_MpE<%my3=LrD%ZMT0VP>QQrj^Y zJJqpl;Sb`Izry0Ah`xbnC2!5IAe&m<8Cwh!m6x9lOkWLxCfUt}*E2BM;><Fqk2i~i zwd2xDon=5H@tfv0YntgWS1&pU!Q}a_4m0>yR%|8Pn0rIhI-+9H`jDoZ&Yyc<XSnW| zZ`#L~p6@<r<wYAqnqC$3A_kM-mFnXFn(DvWgD65GAVvT)SlmB3m=h2qJ~96%9ryqH zW$$nvJ0dIwJ59bFJeK+gX~(T6HmChT7Lu(5>ph4+4Mx2x4{A_K&P*TVLG#gPH>P{g ziw0Hpj3*!NWJ&xH`=BV97M%asdAJiSrzP_s-<PX-OgqU>Dl^j_G<Fy>GwBZ|vpEE6 zIni0IQ;&dYP#Rs$wAQFo54oB2*N9(ucQa_bl}1<5KW4}sM?DMSmo4J?Wn<Ua%krxf z(D7wF=!CVeF<?)C_B2t&5=lSbaVjxkln-jora1Z`AALrud2l7}HdX^DAYL3YqnZb> zgy!rS*$B#JM@ea%vq5Y(DY6(pAm1<j%SE^G<Rr0KcSeTf@c3=njD5Zu<Gtpdqh4R~ zo4brLO}Fyy^daXKn6Ic=zVQv9%j=Uv&Nk?SQZAeB#Ar)dGL%puXUjT+iyRBDwQ4rs zE&-~)hC@GF5dS^wBxJC~hD1v6cRo+;wJ3z;Qa6j#)$6<2xJkMJExMh{Ejp6I&i<r0 zjY3L3GUoGa``Q${it9%!4?*9?n7QkDhKMEP6lEsF{@nm<8m%5^+D(7*lSyrZ1N-f$ z^4n*{ev{zqO6+r2jJ*n|$L-Ap&!Ry<Ou*Qi7X0QTy<cTxCGN<H6ljs6Z8%G3IiRBO z@aj${2OzQs%el6s3Wak#h?k-+sE601r0&zHvR%ofI<x~d3_`HOHua%2p)kR!-97%y ztS7)-=<xamDsU68lESMcm-dYI>QzpiBLwz&1(JU2!Afkv>-3V~>0rl(#7rm6(E8Cf zECIQ*tr2ljJK(U`s-5ugXrTMjA-Z}=!>jp!%8M%+#$Z5mH)CwXQnY=i0r&GpLHegU z7Qe10#5@odM#87k2rSLegpc9Ux_W^ylS<~Q=J+4jlg<b9T|0e@0_axVrTmj%ZlPP@ z5!E_{AuzhEhnRr0b?0f1`3hLED7~{Wm!Bp~IdzQ2>_C`fobxgU5gH<@ldQj$V=6LH zzlcycl<~Ciez<acSLhnwn7v?r)(>LOi&7er!4re$Ib}1&Z2GCbe~dY%i~~Z33j8x? zDhK<fECg!a>~unQz;ke?=yfqNU+8scJK?hQ+C02ToU@{dngg`=+CCi->I*07#-Asz zp-SUjLKAVXCV#mh&vo%lr|75b9*)t_M2p^fMos-e6iVWlfZg^evz5%84HZIVSs$+e zx<gUzYAXK~##sjgLBWE<W2F<unn37Ck?YSdwt-j4h|tuQ(}I}s>U5zZ{#;l_V2SG& z=!DC=;*S{MjtN^k{rGUOGZBR7&e{=SqD)rvreu4Kp76qSD6Igwzr5yoU3;!$UD(2p zL~n68o`_FFpC&&oe%cJf`veYC)9Ei_@OA&um&C7s>bRNaZOmoc^h|GBIUMnMAlwYs z49|=kCDM#B<plnTHnXpLSzi*LzMw$sA$zKI?Lgphm;~G&@t)`04G)h*l^GaQS-#(h z2W@<Y*<!_&O%?~Y;7|_ii<p!OH*<B7K36onzr#1oyi@C(z_W;GZUzQ;3ZS-UIhZf1 z*G2VPlRK4<bNG3<v%!$Bv(`{$Tf-HNl5Bl(@M?!govZN|UqtNyxEX;NFCml}W1&f` zlXxUw?P+5mw^FWw+=}t@9Z|3G8+uJX#&EW-mlh^JqxhvWn9-IRPfJDIU)rg&8|mt| zD3<ed(?E7Ld~ZjY#o{vKP-8!WA$ORt(On(_46Ra}>D+tiFS_x{FH5YFNLIcdW8!zc zrvPvoAK+shd?0@xO#|*NLAc4$%T>t{jd^NYDNE;^q$ks0x!!Qd4X$dZ6y%A$jBT@b zFeAb+jcu0Bh(gO~%$mRXL0Ohy#oeg3zFSWFfvyW=1`d8jH2oRTb2g`_abk_g2KN_v zh{WIgp8}wqrm`ykp$mKvVZii%))uAxH!0l`3?yc7Y0&@XYkkOpkO9Q2g7G)k!uvF1 z)u~nD#ov_Jp*WEA!j>}sO9sKy0(UDUy!!X6K&WdpatV?7XnmtEmW*$+l_0v&%$-r9 z<v54NHcMk8BT-+2ofE%)L7>X;oCZGX`poPs?)Sc#?`8~Zv6L6;AHIfC`EF1z^(}mk zWbrC9n8{IEubM!;te`BDJpuR3PibTg9sgUmI)UfnK$biEyRf|n(sPw&w*R;JCm7#C zLkYLxqB^q}Qr6oHGqmwuVe>faF%2q=UuuZDdu<OZ&|;^U%>Nor;^g-o92{(a`NgXx zeiT9f7r_<pbcHJ)_IaNuLWvB;fC7LlnVlz}pw*R>$S<?dWp~h~>af^lrGq-=-ZP4L z+Hqwx8Z;X9(UE^_A}s3kymQL7yazcHuE*;MvNQMjvoxQFC$SqBYMT_i8P|PuZJMYy z;FMSC4Wu{*IOar&GW4!XiZy=764;mZaMs7VPIqn(<dl@b4`EUZudlF=p>~<}Clvg; znZBbbQk-)M&~_<#M*Lz{jI6YAnc1n;i~zZLFLgqL43g$?s(quRetvO%77Q(M5mv!o zxKpg$4zy6C9?+&<=?jnaW?Y5J8qAQom$=sE%GNtluQN;4GnO6ip)>-k$@H3jBQBxf zkZ9%6b<s0phmn9ymg@m>;7gjI7OgL4KG4pQ>`H!m1=jbHN_83W9#6LM&fg&86)kBV z?1gTLUnm2bG$-8()-5@BE`!W<%?!}wv7L_DQ1bgAb#Iny=czZ-KN;EX*J^hKk$Xe9 zXFp+9o<@h{-;le@Slyq5jmIaKWbq4_=7*HlNY9_Nd@7G1+Zkz|6(EENUAH?-WJ`+_ zKDt=c(mP8R=vRW^x#T>RE!ZjL6$jy4dBe=U^52k87H;Z$j;)#QacL+CY;gv$lqW<b z^E)M7-*oWgD=RkTU-ysbST*$|K{s`ZO9T4_s)M~PPcfj-o&C`(vaCRs#)Q@@k6jo? zT;G88%7NFRbZWZ5v(ag<Jf^Rsoe>rGaVag2sFvRPI7`<%TGA(et=wjITw%nLL$)I_ zXO`M;L^;>&gLp{fBK67ek(gL76-s)|DV^XdHWvR{Jl3SvdS|m2H~!JZz$6eATVTUx z#!b)QrT?QVG?s!0k=xfy{2!9LFPqbxYo5DJ2}23)UHt0yy)W>480$~JL3>ajoI`0h z*gA^xNw6&Fq>Dni(D@gGLeTAXLgt3kFI{CluOqWJo6M}1`VP2<CwkbbE{pdP$|NAq zn-R)uns0hm-`i}){Ud}`@gf$EEfg0&S#$oHa!von3*+6sXx7L}wJob#ACwwb@F3O0 ziFgFmvekfe#}F?q>0}M5_gOp1e^AidLzOvv6IanOuXiRE?#!3`O)s8XafX`z5$HCf zc`{gLr^q;h#Hp1pcUQowKKS{`c_TC}`OXm3_F~a)=+)W8<SIg*$KbY9fo6K$$mx5l z_gl&@8EY?JL&Hq(@*s_;iejAmakiVx3Kxk%rEJ@x_(L)_b+?(K_yV>~T_jEEGY()T z|5ZidldsyI9abTC!k3>ylUVJ3xWjtDq2-|a_1TGR7Acg?hU-ob=(6$<kjua#8~VIF zC33yy;IQCTCl$U(i9M?+e|xTVmKOv1IOx50v^j8IqgwyYva!P*+R;_2Dh1mh@CEJ0 zszScu0*IUn>M*kzm`*<1e1ydOJoI_FL%R32eAbx@%3kIMIe+QZ?1z}j@mp+>k~GjA z*F?1qIW9H|bvTO9yT?Ceo{&nB)>RX&bg&-eXwDZeH9XZHQ+NW;&s=|vU(Rwb1e<bz z-k(p4feMo$x5=&YaIL8qVeC37)ib?)NnTf_is=i5jot=lJ;W|%o3~~BCvyz`KSWJt zDz<Z_LcDkt9>1V$5WIHhy6Q@o<6}c?0!DB0@`2ukfTR$6Vc)Vs;%Fvl)Enr_1$>*a zAF-7fO3NyeY!<|za~Ss@!<U=wd!MDWlj6L6AzGiUm&+MdC^>fI-ua*|;!;!f^B9qp z3z&^6Lc7TG61;f%(3U5oo9+$21w(oIq*uE#YEiz_TpP9;l}9Y>TG%w$oM&PWxe;i; zZxY24kY%iTKVk!W=MnZ4+N2|6%0oF{$vr>B`*w}IJhYkYCiQw__DHg;<^aSx>+A4L z|3=F)sx`Y<_RInL2zv*b`jOY8?My@2W#OiVZ}aA$zV|b^ctzWr1e<P(07nULzIAr= zOL8>cCmDL9%%n-u6B1jlLRp<0N~bi-PuPGV6OB={JtiQJ{Y2W3*EK32C{a!1-oBVf zThH!_dg9)iiHr<5`60Wt?C9RwV^~hq@1*{0P|APqYq_{~#GF$wRKS${thLXkK(Wg+ z2VW~+wi$H@Vjy6L+0EgevsK`I2_-d`=~Cl*?|bGyMxb5RSI~UfJL`TFn0W0JKRr$} zOOh{qwaef%Euz34^Mk_k3MnuD_F_RH^&Bkws7v5r3DTqag`Dg9P35GuP66<gRJL`z z`U<raL!t=Gh@Zfd&*ds)!M~y#Lm$X+mPb2uS`Q#-5CDSq>XwYhIU@vMmQfy@%xHOC zXBJJ^N!_ZD-Unff+e8SD$*H*C4qzi}9*5{va|%szY3k?;oxpa?<IXM^p+~`xN1)oQ zx~uS@uyWEL?Iu63a3X@WM^)!cs@uZa>R?H;a-1Y~Ib!a8HFPz*P@Ry#=H*Y-)An9Y z-BG3q$$kUXB2x`gUpK33;NN+iJsVe%Wi%q;37lwtE})QlcERj_kiyp1Z!Y|HsH{_N zw-K9HS<m5C@8`*f9vh8yoZHHU57Ck;ww07ywtBUNBscQCI$7-_zzAEfQvVKcx7ODH z0=%{G4Bej7LW)HR$se-DQd}4X%46&|cCVJYGbvq~W=flF4gM+p0smCce}lc>y6K#; za{d=_(zmQ?_sbU@F`?Xxu1roH0eSTO_?@hbBnyE%P6N67Bb25lOdm(_P1r=v+c9KP z_T5a$O1QO4FK=~kS}hKw?7EaH8&>>@gAO|HpJd;3uMQ=qv8EAGwk&^e=IA|du_8#+ z%sB)K{@h>uLKzSCZcy5*-y8~K3~c+ke5wj6G6>TiBWbH83@7_OhqJ+h|G6cy-lDc& z8uQ$M`W(kaq53PCVe|Z`=4y-?_#~;8Bp-PuC#aLqklfDs-H6>@BF6l$FR<%0$@#5P zCJBnKLV%EU;C-2BN3$i}`ki9wb^?Wb+np3;5N~iJF!?~!W7j+7wIX4o$>a48W?h*l z16dk+W?B=Oi>Z1RV_pL%x`_+edRu4jUEPk?=r*(<4YZ*(`-r?AkE5`O`FT|^7uGu% zw{)5F@C_W{Io25cwunB~Ja6lCmExoK%<T(oU};zy&fn_qeU|tx4(r(I8@;cBbRe+y zRV05=tam&EKTudcn_j0zn$N#G25U4b<jrih#0)sRKYe#}#~5hx)Tl?IVbo#m$Taz* zfMt1<`*EVC|MG^qhHfWGm$&W4vT2=|5s&k*g*<wr``Kgsan!!$T9#jW3g{SvB2;wz z<@VGxz1{H?qV=JrCNsu@#OZ8~4Z>#pP`|NOhbau^#t}jB4l4S`bO*J4L*sN^WS=TX z+?V$2zycHPA61^EWqxz<1lAg4230<q-^e-&LWL~KTfC#!J`9>}Kc|GK2}e5|W@{qx z$z8}ICJErv#kF4%X+nCdKx4arV_<epxt;|<cyH7xzxKFCy-7x1C|H5OH9Cd;2-tQT ziBk*!*h_iOvr6tqfEHtt|0dq|H$|0U2+#xEa_VeZfA+Wo5AY<Bd6e-V*76hK-wgjF z2KObT<=9EDfv;*1dra{lB5L>{MAoa9blvFuX6MMH!3>(}@0+EsK9Hbsa=;VRGj=uS zOxwWJw2S95@1;e*F~0ze4GYwmy^*3>IBzZP0aWH|pH}9rMhclCl`k^QlDUnUJ@B8r zRPIbc)2l1}cCOa1j_AM%6qM2#!DyF1C@ET3=$cM*<}-Qh;>tczElq^$!l=`aevJI% zK>v$u8(Xf^$p3KKuRs7$gqke<?^j?gp30%Wi^qaz*`W>iu{LAEA*+;IvL6*ow)!?B z3N-<V-m(6|*yfc70MXJT%ox+ZM6Y0lUv%C!x2ZipKbP43;cPjB&bQv}HCvtzP2tu$ z+nQiuvyzM!Eq@~FF|_S`0K_FB|A|Y~Q7@}xHANEU!Un#84nP?BbTQ3?Zv`fzMQ@*q zdPvomZUXz=%^Aam9~4Rq4{_%n$EVLgZTT@qUV!`=r-rX^skm{+s_1ivkZv$xAXEZz zGw;Hk_@oMMlbfo1IzJso{J1oN(;K)wl0DMBrnE<UKhi9M+)m^(;+?f{y97KUn#}j# zRBJ4P&Wmm0$E6UQq~P{EmtN}7|M&MD;PKU-F_tG`fy2@_CzxPcN8!3F()2vZ7RJdZ zMtF$a{o|)<t&d}4pUxQLkalum_-fA@50J3X+fIEA%0CeH4j!!5(&p$>mVoOFI}dYS zc<bvt{kF0LC@Yacw);cAxhqz&w`S*bizt;N)?BrK91p8cJ{1;ly%HCqcEE?6?}o4I zUrI==j=cKZ4=#<=kPt&ikN3O2S5Sd%eFVdoB9g%_fu9TdwCK|;d+d-v*9K)YiJ2Tw z7}kq3r|C$qb~f9}#VUaO5((ENfD;z1gkwV@y|^2I-YD*?@A(H6H9aCwaX2OYw&<~2 zRMRb~`|QNE@rh`C*Nu-W7koJtTNL_`<KD9IT#Iv;@V8!mvr(#vX7Bs_{H27GFbBE2 zPhfbNeNIAn&3cLNy0Cb~n=`1Z9>x<|o{!f?Tt|I*6N;<)s-!WUTOo;QXDJ8Td>ETR zu2Bf*(-?!w0ED2c#iPuX8l~L74Y#1z&ga#~nE*I;Tv~-wsO#z|=Ir`oE3n%PwJqB? z2fP*dT}5sZ(Azc(#0To6EZtMh2oM(S^A*9FoQvCaP@r&$AtJnsHI8oIjt)fm3XNy_ zdSj(90+x;ZwK}Yq#S&b+xZ)lMtPRc4-qXJv?c<F6bpFQW|Fg*^W3?p2{5{Q|u>vR^ zq&7w4oPE;2H320c0qeT+?Rf&)?;rm^`+L^b0v2l!D246MUft4vdSJ}lcU>f?2N&vk zms1isOzESlou+R3)8(^VMZ(huVqsqsr<+$Ur`gv`$6hVaHh&<yq^=@xN^KV?bfy3> z@<|X*bD5*iNnZfR4os)CZ!WMS(G<yU@JHP0atWZWk{s;uoJ<PEu1SHOBDPx_6Tev5 zRKlndDV5WSOivHwk(KR)*_$z_EDt`${5E6%>dvx!JLl3QT_}q7gdl?U_ebd6G))%+ zafhwk)|{KlMj7}9T7h|GKuFv_HfQvqsQ`>76J{^_S0hmeh)f-%Zn-~>CLhGlfG85V z6#tiHEqs`F(IXQP|B5200I&JtC)3|6HD91{pHXo9X-s&WCx9mM`aj=HK7#Khz{i%z zQ8Sz@-4<jkg_E3SZ~M!w^iuYdKcNof1(}=?9LpL7b@PHYBmh7>#3y3MK#M{1`>ob# zmdnmGXjK#PRjZb=?n^+HSy3sVOk<eF*LK0;&={!{Krbka@2HyDeFNHiSP&&j5%US? z?XlK2L+Mq@{t(d`zlmrD>vdv}0Fue$>v-ATbUo^(F{g<x*mR}R6m2sChR0BxY;=kh zc$n0*xoNCKg8al(m0_25!bVJHvgGzZ(5@)LG+<O4h-az%M>RhMmaCT}FtNX__+R|; zZ>;w#)@Sec(@cmB{i%4KeJ@JF!^5+w<bN9=xa{wC`CXVwZE7VqOnGD$b#>38()BO` zZh^~L>YZw(ymNiZ$x^*=T@R1?+j^tL;+>+1KuvWtjO%#ZTLA3enyIsoFaLHq-KBeW z_0TZwhC2_ua5PGC97%aK<l3`Onk+%&#Ym;oI-}_XZgYNv1Zhme#~p3`{{i`>b@>?L zbWHHKH$;l^0PwOiE`{PhnAsj!#N)+7e*D#`hUJ0&F^||i_Ro<=0$i^Te`Ean297a6 zcjAFRcKkcEUj_MybS1mU{Zb!PPAyM($es+M37|jtAT|?<|EvDvuNmUe|Je-Da1%ti zrn!W(@bLx%m#%_f#)gfUd=d<(UoUekz2ETmW7q0)x!?4~;g=j!{(oB_JZ}1u%*sy~ zO1a`#_F@c}t=`bjX;A>C2Y8J5f@0@7$hy@UY+6q5VLptydU1f`>0mbzbZSPff)QXv zc~ZU?eWsJI>QNfCGszs(Y}!zz@sGEMLz4iyYFK-#|KC=R#00vkWsM5M-&X#AeHlP* z>5n_C8xLdk^XQeJb>D!646hzC*9t?LP^!S~QiQ^_0%Pl?<<AISkT2ZT^edV3o>D~V zG8K{qDa;@lO~|*~KxyY&$mY4`Rj<0cFMXqey3&UN&uQeRnWTWK_iAQQaii3Ou_prd z67|b`FZb*Ap-g;BZ~eq73K0=ZoA~(9E4%eVB`dDJ-dC?Q=6WU1(fJ_jXH4K&k$M?V zfDMbdD!0fiQv@O5pQkZ^srY7@GX(}gUU@pf!W%=m8dHjei+-^Xog0*k<cOAO)AW*! zgpXKG09#4$46E4=2JjI~{DTj|-8Al#dO-jj`HF*EFCi#?0<3*xb*F~@>iU8%El=KF zTKD6y)i<qYIJSO9D{O2ON>LlJ`MsdU!ZP>&Ca0idc@ft@wtT$B_t6AUtFiwix3u|A z`|Myb{Y+=Puntsq8~56b`zC+lY#rb=<SP2?Ryz~<m@9h?X3m<FA>L=b5kd>27P6|O zzN$jU-IQSuxhg^tP{UQ&>h1NJMd9%eR;poCAm9yClAT_U3g;21a>}~8T#aB!tPM?L z{hhEH4}{zM!9BP0LY;rXJwG7y#HW6z4O>L_Y<GGr`!kbAJ&|~x`)>_H4t~6S#IZqM zx7K-Nx8CA^%vW&%aYQlvDgWE(@M<jj%;#Dy^Jh<`NHT#MJ%&}DRXUhZIe)}xv|;9$ zD^3f`-}!{x=(sQy8j<84{w?X+ZZYJduh~f1VOp_KFJhZHy;QIW__BdiW};-@;(_<Q z#~IL0g%4?ICxrt}hKRT}q_gBsr0XZ6YZPOjV3C^?YEyT*yN}GWd&H1K19(6L0ZMnH zr@?6zfD=PpUmI{TEc4i}z5NI-8`>VrLz#FBg8%xSw&;BG138eKNR%I&%q30c9=Tk) zzV<01X*q*VFbIovMvCI%erb$KQ8%eAt7#Zyvs;lkp?`<NtXcnA_h6whMvW~TF)3a& zNw01us_6EY(K`LO{tT(u*e_O$Uq29SyHYlk3<;R+eYQM|DYvrq{1C($p4xur(X4lj zTjf~LFgIlUGxkIWfcf!P1A4+Aw}Y?7OTb(#Q#tVCkFGzS0!)7cMLtA-V;pK=b}Uhw z{lEQTKF?hNx^A3Lu392=fYCWS#ExpX7}0Z@_|nnQG5f<ez1)}I;Lu(fJOlw&?m1!Y z)RcPQ7J4aWaGzZO3HNzCrP82wsTqSVJQ*|ogKL@u$hEi<N+X!$)Zp<D76+%9fFbAc zb796gf888_y@MLCcRZtw`xlq615=NYaOgklYALY!YFx=?|2x0GeK5-O2YvrLQ7y#* zTMx|?HOaqgY7ZiinP>+`7t7W3YUSC25w!zFOLns~wyWeieq><Ew@e6=tlJ+iD3inj z`39TLexi+e-N}p0H9>3Xj5t(BXJ;CG&0VH}@C-zjoe<BI(f?&u;yDK5(x0S6A1}@) zM+CDygB9c1t##u&=?pE@7MihL6~IEIdHy@wJ*Tvy%FN6msDLn_&;lm)#f`T+%VF8W zYMkB)ShuhO0^RP82V?c@%vtB?KerSvuxFx`!~5sj7XcdK);1geZzI?OonYs4@M~ez z?18PbC}gKdQ4;vYJsaqF0TiGqzsiPpNmIlFJ%cf6=FJ?yRzrbd%Spt0Hm*7Wy4l-I zA<{)Xw7mzAP+Q?He8Jn(v55tb11s|5DiPZy(KBdKX(2$EX_l0=qA#4PJVncX3g9(7 zY5=G`+8%4=A>9f#0M#a$rn!v+Ft<(Rknl~u2*6vShm%%a0Hcv6wU-x1N#B6>rJ@Mp zlBJ^HkwoRxhmZoVi+RoE!5Oa8{dt|R=Ug(Fr2OM&u`l`ht>Ys22;Ll`pjE)Z&z?tr z#R&3fMWfAsgaza|su$b~71M=%c}>5dysE(!_YqnkWNpG)wCf@@zdT$n^uE6{2iyjl z>E3s>%3kNwrVNC9;R6B(4Y`^Ez@T&*m8LD2)c24lbz6&qW(JX|L<k;#sk_=pRtGS1 zZLhP52>?{JaSz#n)I;2td{M>$e~{r|morcz*Zlf)yVER!Tx4M`Y?b8Zps)zSzAbVw z=U@(aD0ntk!fydj0Ruhz@kR0?jD|N*Pz+nRi41Ft+OlFqGB<QMz@8!l{Jn=J`GJ&_ zNwx2#Gat>CURIsJ7J+3{%{%KjZ6un;w;#cwIR>@J^UMS8a^q)X*2}N<Y&(fe3F{?h zKbIK72bArJ3CmysSn@8L({>KHP{+EqV~W<=x$VV3r^FpVpDRm|I>m1Du>knJ3Avlp z!l4Vm^@n^i0<F#X=<`CjY7Fluo9~{bH~{U0QWZW@40s+|+cW#UVXNfw&w)b#CYhZt z0f@T@!085b$+;Ij9nJv4J=vjb-fc5=V)5>rCtMrpGXqd6oAN4KM6bYYew98haUpS% z;A#om{<1vc&7QcVsp=vgx3??c7?IZk9`F#xF(eqX2z0)ao#Ju@{fk#8D5Q>&!Szdi z7{`E>;5`T+zz5O+Wc>>G+qHYYjP2d+4Sd3@7)4>e(lF}#)d-R2SH@sH?t6gN=4oCI zBu-jvzB?;Dsh)REo_@GSs0FwMmI#qOyn*y=m;2j`%}QmWD{*Wg&Qcaa8iQ-dyjpkq z-Dw^S0232~NTAXJ$_^w~Vhe5XZl6hxKih{Avb`)#t$He+ST*$l12@Rw#YHbe>Doc< zCK)A<+p$rWLmyK$rxk5+{WK^Grp#?f?odgYl4S#t*<&{v8s0Q-?lvg@tSEfdUQxYx zyYF&URNBNZ;(GZ0gm`_7Cu)Od7kQ7|ZpJr$N<W2U7v0$v075n^aXHQlND~TlSUrKK z#kFGWC*%%4DVrFa+oe>TH1PHU0T046>7Na-c7}jegVXt@$l$hjYnW#xcRv$jGI2BA zTex}JBzfIV==E@_pJy}ZHq<N4dB$vmWJ(yKSX5G#6cr#(4!CD%_HPcFheYl!S53&x z^qi-SB^9&oQO>D_%ZPy@D%cy2-{O!S?4(_kyaYqsPmG8qK0A-Dh_7JY;<`**Kx%XO zfzB?^2LS4fmHq5}`2ez>usB19)F?A&wK8~7)`q3(b#u;T?~ja0PxtUGxr~L3$64&x zjc`nKW&u_b;xoV_L(s$Wy&VVLd&@YB_N##VT%$Qk=|;LYXv{;>E2HbdGQ=>qE+dMK z?>@4#U>HO?9>%lq>9{aVvGMGej^qQ9d}@F6ad+ywjDV8UW~xh48qe|Zh!lANV207i zBne?@s?l~^k9*TNsxY~g%59a@I`37g-{@CkvLO9ioa=wrK4oLFt**r;`Snyj!)*2K z^DNeD$+5MU;J!rDT8rZEf#1Ud@U^=`*)vmTtMY8<Kdx`2d02nsu#0f47z?drn?j!v zX$6LaP1SX$+IF%|c|@075Q+Vl8DL5ayVbYrcxFPyz`R%!PN54x%_eJRC6&Kawr-)x z*g2_?YXHwKaGtZ13OLR{DIqP3n&fFQ8MgxKIkPnz7NN2<o<>JK?7SvgF<2}iJl$=; zWNmpWhTLd7<v12dc=NVg7TDpEq)(T92#OHVdP6Huu%=D@-VZNS6yY%mJhG3E;S01+ zON31_Ivi?_OInbo9qz%W{D%TiU0!ZNd~3K<(n@KpD?biNy$s)$?U$j%JcE`;<TT%Q z0jCza_<Ceed57(|s-jR1Yk!!?;}NEsB|mf?yqf%<%Bz0xjn{+kw(CEvI%Z-MhzYdN z?bMr{ZRe#YNz*y&avao8NhO80*UzMM(^@4DFfY3vy?W!f|FZ{T2eDPLHL~?|kEQ!k z?y~e*Z*AAXNTjFJk%hY~6X@mhDIWyB!#_esJRb#2WVqO~3Vqmt1yA1xp(YTs1FUV5 z;RiveAMUS4Pk?%5mBW%^6oaUj$$x%>uhp(gKFr+&%-~CPNVm?Qb(rJ=%{20guGN4R zB+78YW5IAd-RoQ{Qa;hFVa~oU@#Fc4iXe7?VMcV!Yi-ZVxhP7hq%>Oh#hPJ?mNL5+ zqvC2C*h5-U5oA^*IbOnP>q}Scp-yd>Je;^yp!4!{q|Hp15YslmZIkMT92P?Aj-Qw2 zUdW~`L`A@>?pDQgt^BLB=~`yF^;=Nw8f!JxQUDx@e<-6^<`mia5Z@*V2mfcX7c&eq zj%v1XA^bc&ooSxOJuQLl)4^6>A^Bvg+MsU`%&iG%mlPD})Ld1rb9P<QIGs_eotl23 zu7*??@XhPbef5tO1oo)D%{j-2?l_9nqZLMvRnM3+aKNK0U@ikT4-WBG_Ts|ykR-B2 zU~w*B-Ifjveg8RuWMV!e{fhpvyn*X-K<8E@RtlquVQZwm>oSsqR0x_5;aBtv_v|j) z(-A3#+ubfBl+G}sluL2G_cesCJ3nJSR$RsA!P|PeI!Gvl+_3U_nOWd55xc~)qKJnI zA#>Ln6Ee=>8_}UWR$Sa=Th{2T^b_R5BSNDIbxn0bq1G}eFgyrC@u#c6R0OX=rlo(d zg(?JN23Rpmrt;}ZgIIM#qu3gnf|89Mi}iIATIbr$`}PCFnUFPF>NT)tm(*&Zh|lF` zP4+DXi_ZAl$*r{bZ&oOUrwqC$)#ZNaqC&b&>6DWkR_K;n86HxWn`WIx!^LYs@UHuw z9Q^#txJ%9kAq}HpYXB(cRRU*DkZ=Fhmo`LfG<ROU2s9=3k=Mj#AYfmEbt=n`Lhf0{ z_XZmm#jfrmadOC}-|n`UuQVv(jTISgnJuKr6{)LL;4Lxxus{2@fIH>4R8S-`ji#?J zD1s+Jm)f0mnbLeEzMZEJKNKK|NyQ?Xf@M@qX<#)orLuP_xW%~ed%DMULHHFSLi8Wc zqlDy0By`%zY2fYvKO&c1!lmh~el!UUrFoZ;Oh`|}`5L`kq{NqUHe9n?6ymPWuh7a~ z$iZH>AhL{O(CoIaqu}zqpqxZb30f@UlDm6Nl+(bjEHgygvqOCJKAP5&fV&2;do7;| z(wM&vf^A>i3>?v>xwdYxizKay#IiBf%;Xg5661`cbs<|-<Z9w^g&L--g_ER_=Tcb| zSzmgcavsrR=3ezhMmzhMbQ28e9rY6o$?W-%u>^XK*353k)<-z9jXY|zufd2wpHX^t z><wV_5^qqthEF<9jN~?cJaQ-Ag11=Z+3OCssPuYFo?GdmDS#kg8)x{FHy(0LF&XL{ zylhlq6$+Vx9}*dXuvH_RA?UOqu$1utyT3I{FS{FNZhRN(@gtdIf9@dD!B-3rkxx9X z?fwhGf$fI(guqO%xy(i=S@VRoKXl=+%&=`-*J0pUTxu@w#u&5SUKl`5%1e>3ET9#4 z`MlVE*feZ3iV<$fFqEi8Ok3s)UUAyurG!^!7B=s*he#<WJo;w9!XA-v*u4!*ZOloh zS}uu$6yiV%YU0RwpsFW>RV`5tE=02Zj6G(kSb;H3XqlOC2(;x`)Rs~ZDawVm3yeS$ zzh?`%q7nEpO}jiyg^6Gd*$|-xjUv1FY7<1y1+A<nyGOr@=@V^npB|cl2Zi(#mo-|I zMXm;RyuY~gyyLT{HUl<-6{zrpCMr)n<UH^nlfF~cS(A0tc;8IT?+O$I`|J~<S9dK& zAzQw9nYmH56<06{g-?`4H#?HvWCxhRR8j34u<Nvj?gIK=^~xzw7=@D+UeiN1CI;Y} zUi9P)Bn4~+;-uT#B`(|Hd~!x;+C@z<ic6s~S)a0rCIh%L4%|ayAUx|t;!LXL`fm9r z#B!1X)ZG2T&qRM>kxwQ-ggKyF&q(}R8WNoch_{k@dc(j!(s@!M5Or`&GWY!n$dp_j zq;rT&!XF~e*93^(X8SAqe*6jc#z}!#udWEk<PU9^k%+cdwqT*s^1~6;jNFo0lbKHb zKR8@g^GMP^9Pa%uA;nfD2q{o;Y&?RhTf6L`E*E8x&FkC}<3vPXAZY!9ScaghUGGv! z8N0Yni3W5!sd~$HjSfK<un&+#t>nSta;g61`maVp>_ZW*$M5j|2A#h9fE>2RESC1K zW`Z$50Wv+pfAdd-tOp*eUN&+wM)dG?CLG)&Noff&wcoYNB1MUS*ZO?kDF2PswwU1o z<oh7unI@l<6CR~+?n@<xn|$>;d385NxiK8uN^Ggxlm-_(6vn@>wwu(*na~@)2pybH z)Cj4mslgr30B<$rtDmyBFUj*bfxG-~*&~4}Xm877?14&hw;P^*ogv+qvgkm{kxJ{K z!JfXgxBxE9!Oa@m8uckaDXm^=xY@Vm*uXwQ_jzr_vx24C%=SEz{p+#Q7j4W8C!%24 zJI_8g?-BXlIF73~Ck~Z7DPhLjqr2UJ3q*G}`ixl5=D~;;iC*fRQ*6;uN+Mu?Bz2c| z6wki$^zVaG_yT;*!jmU=RDY!k#)q;L(%`y(^+F^7M*`w!k$;!6Ku6LV&9VOb<ETJK z!h~N|{Cn9Q4RoY<$AaX)Nrl&l02cMtljt2wSlr}2Rd+AB>mnUpO|MwcPOo6U0jJ$D z!+PsEPYBu`coM8~f~xUawmy7SP2WHUz7D{p4QgdrlyWAIP*w0ZX=f>3R!{;p^4{Fs z^uU}VsIQ_75aEGJGm>8}`P$DXg~Hb~Ct>D4?4N~lTXm2soiwsX$kss^Vp+XM_L5+A zT>-FeMS$d{r9z)I`Hn5T$}~=Q@i)s#;Q%~UBBjY+If6>*Aye}Hr_jHrdXQ^2z9jzX zDsX6cfY^=y&h}raGxmqNwR|6F{*}f;0^SS>f9`Lk3*UaU_EKFg3uEHqcHawwqdTDL z-hW)`cpKPXa2{R7t=Q>lA)7{%U&T4-IW#Kx(U*F6SOHnXffg3fdSODG;#a%4yVt9K zI?HrB?*_|LMRTgFoR>0q6wMNX1yO6@udkGSU^qwFlD`tjH6iE|O6A3&ejn`Cla<z? z>BWmGG`uT786_<K={wO(-be#B?5p*MYxkYn+asdValwDy38jaRukO=N^|x(Afrmv$ qv;H@R9!eAx!6i!nE9>#((witBq6FIDRf7ZmNWW2#fV_V1_ul}RtTbl; diff --git a/packages/logging/package-lock.json b/packages/logging/package-lock.json deleted file mode 100644 index bc84bf13c..000000000 --- a/packages/logging/package-lock.json +++ /dev/null @@ -1,5597 +0,0 @@ -{ - "name": "@optimizely/js-sdk-logging", - "version": "0.3.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", - "dev": true - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", - "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", - "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", - "dev": true - } - } - }, - "acorn-walk": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", - "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", - "dev": true - }, - "ajv": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", - "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - }, - "dependencies": { - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - } - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", - "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", - "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", - "dev": true, - "requires": { - "rsvp": "^3.3.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", - "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", - "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", - "dev": true - }, - "cssstyle": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", - "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", - "dev": true, - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "exec-sh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", - "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, - "requires": { - "merge": "^1.2.0" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "expect": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", - "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^23.6.0", - "jest-get-type": "^22.1.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", - "dev": true, - "requires": { - "bser": "^2.0.0" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", - "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, - "jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", - "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", - "dev": true, - "requires": { - "import-local": "^1.0.0", - "jest-cli": "^23.6.0" - }, - "dependencies": { - "jest-cli": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", - "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.3.1", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-source-maps": "^1.2.4", - "jest-changed-files": "^23.4.2", - "jest-config": "^23.6.0", - "jest-environment-jsdom": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve-dependencies": "^23.6.0", - "jest-runner": "^23.6.0", - "jest-runtime": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "jest-watcher": "^23.4.0", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "node-notifier": "^5.2.1", - "prompts": "^0.1.9", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^1.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", - "which": "^1.2.12", - "yargs": "^11.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", - "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", - "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^23.6.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^23.4.0", - "jest-environment-node": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^23.6.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "pretty-format": "^23.6.0" - } - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-docblock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", - "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", - "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "pretty-format": "^23.6.0" - } - }, - "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", - "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "jest-haste-map": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", - "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", - "dev": true, - "requires": { - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", - "invariant": "^2.2.4", - "jest-docblock": "^23.2.0", - "jest-serializer": "^23.0.1", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "sane": "^2.0.0" - } - }, - "jest-jasmine2": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", - "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", - "dev": true, - "requires": { - "babel-traverse": "^6.0.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^23.6.0", - "is-generator-fn": "^1.0.0", - "jest-diff": "^23.6.0", - "jest-each": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "pretty-format": "^23.6.0" - } - }, - "jest-leak-detector": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", - "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", - "dev": true, - "requires": { - "pretty-format": "^23.6.0" - } - }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", - "dev": true - }, - "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", - "dev": true - }, - "jest-resolve": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", - "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "realpath-native": "^1.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", - "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", - "dev": true, - "requires": { - "jest-regex-util": "^23.3.0", - "jest-snapshot": "^23.6.0" - } - }, - "jest-runner": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", - "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-docblock": "^23.2.0", - "jest-haste-map": "^23.6.0", - "jest-jasmine2": "^23.6.0", - "jest-leak-detector": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-runtime": "^23.6.0", - "jest-util": "^23.4.0", - "jest-worker": "^23.2.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", - "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", - "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-plugin-istanbul": "^4.1.6", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "exit": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^11.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "jest-serializer": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", - "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=", - "dev": true - }, - "jest-snapshot": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", - "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", - "dev": true, - "requires": { - "babel-types": "^6.0.0", - "chalk": "^2.0.1", - "jest-diff": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-resolve": "^23.6.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^23.6.0", - "semver": "^5.5.0" - } - }, - "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", - "mkdirp": "^0.5.1", - "slash": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-watcher": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", - "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "string-length": "^2.0.0" - } - }, - "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", - "dev": true, - "requires": { - "merge-stream": "^1.0.1" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", - "dev": true - }, - "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "dev": true, - "requires": { - "mime-db": "~1.38.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-notifier": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", - "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nwsapi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.0.tgz", - "integrity": "sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", - "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - } - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "prompts": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", - "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", - "dev": true, - "requires": { - "kleur": "^2.0.1", - "sisteransi": "^0.1.1" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - } - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "dev": true, - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", - "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", - "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "capture-exit": "^1.2.0", - "exec-sh": "^0.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.3", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "sisteransi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", - "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", - "dev": true - }, - "test-exclude": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", - "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^2.3.11", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", - "dev": true - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "ts-jest": { - "version": "23.10.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz", - "integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" - }, - "dependencies": { - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, - "requires": { - "browser-process-hrtime": "^0.1.2" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", - "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } -} diff --git a/packages/logging/package.json b/packages/logging/package.json deleted file mode 100644 index 2a2ec99a5..000000000 --- a/packages/logging/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@optimizely/js-sdk-logging", - "version": "0.3.1", - "description": "Optimizely Full Stack Core Logging", - "author": "jordangarcia <jordan@optimizely.com>", - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/logging", - "license": "Apache-2.0", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "directories": { - "lib": "lib", - "test": "test" - }, - "files": [ - "lib" - ], - "scripts": { - "clean": "rm -rf lib", - "prebuild": "npm run clean", - "build": "tsc", - "test": "jest", - "prepare": "npm run build", - "prepublishOnly": "npm test" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/logging" - }, - "keywords": [ - "optimizely" - ], - "bugs": { - "url": "https://github.com/optimizely/javascript-sdk/issues" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@optimizely/js-sdk-utils": "^0.4.0" - }, - "devDependencies": { - "@types/jest": "^23.3.12", - "jest": "^23.6.0", - "ts-jest": "^23.10.5", - "typescript": "^4.7.4" - } -} diff --git a/packages/logging/src/errorHandler.ts b/packages/logging/src/errorHandler.ts deleted file mode 100644 index bb659aeae..000000000 --- a/packages/logging/src/errorHandler.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * @export - * @interface ErrorHandler - */ -export interface ErrorHandler { - /** - * @param {Error} exception - * @memberof ErrorHandler - */ - handleError(exception: Error): void -} - -/** - * @export - * @class NoopErrorHandler - * @implements {ErrorHandler} - */ -export class NoopErrorHandler implements ErrorHandler { - /** - * @param {Error} exception - * @memberof NoopErrorHandler - */ - handleError(exception: Error): void { - // no-op - return - } -} - -let globalErrorHandler: ErrorHandler = new NoopErrorHandler() - -/** - * @export - * @param {ErrorHandler} handler - */ -export function setErrorHandler(handler: ErrorHandler): void { - globalErrorHandler = handler -} - -/** - * @export - * @returns {ErrorHandler} - */ -export function getErrorHandler(): ErrorHandler { - return globalErrorHandler -} - -/** - * @export - */ -export function resetErrorHandler(): void { - globalErrorHandler = new NoopErrorHandler() -} diff --git a/packages/logging/src/index.ts b/packages/logging/src/index.ts deleted file mode 100644 index 47a1e99c8..000000000 --- a/packages/logging/src/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from './errorHandler' -export * from './models' -export * from './logger' diff --git a/packages/logging/src/logger.ts b/packages/logging/src/logger.ts deleted file mode 100644 index ff9abd920..000000000 --- a/packages/logging/src/logger.ts +++ /dev/null @@ -1,328 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getErrorHandler } from './errorHandler' -import { isValidEnum, sprintf } from '@optimizely/js-sdk-utils' - -import { LogLevel, LoggerFacade, LogManager, LogHandler } from './models' - -type StringToLogLevel = { - NOTSET: number, - DEBUG: number, - INFO: number, - WARNING: number, - ERROR: number, -} - -const stringToLogLevel: StringToLogLevel = { - NOTSET: 0, - DEBUG: 1, - INFO: 2, - WARNING: 3, - ERROR: 4, -} - -function coerceLogLevel(level: any): LogLevel { - if (typeof level !== 'string') { - return level - } - - level = level.toUpperCase() - if (level === 'WARN') { - level = 'WARNING' - } - - if (!stringToLogLevel[level as keyof StringToLogLevel]) { - return level - } - - return stringToLogLevel[level as keyof StringToLogLevel] -} - -type LogData = { - message: string - splat: any[] - error?: Error -} - -class DefaultLogManager implements LogManager { - private loggers: { - [name: string]: LoggerFacade - } - private defaultLoggerFacade = new OptimizelyLogger() - - constructor() { - this.loggers = {} - } - - getLogger(name?: string): LoggerFacade { - if (!name) { - return this.defaultLoggerFacade - } - - if (!this.loggers[name]) { - this.loggers[name] = new OptimizelyLogger({ messagePrefix: name }) - } - - return this.loggers[name] - } -} - -type ConsoleLogHandlerConfig = { - logLevel?: LogLevel | string - logToConsole?: boolean - prefix?: string -} - -export class ConsoleLogHandler implements LogHandler { - public logLevel: LogLevel - private logToConsole: boolean - private prefix: string - - /** - * Creates an instance of ConsoleLogger. - * @param {ConsoleLogHandlerConfig} config - * @memberof ConsoleLogger - */ - constructor(config: ConsoleLogHandlerConfig = {}) { - this.logLevel = LogLevel.NOTSET - if (config.logLevel !== undefined && isValidEnum(LogLevel, config.logLevel)) { - this.setLogLevel(config.logLevel) - } - - this.logToConsole = config.logToConsole !== undefined ? !!config.logToConsole : true - this.prefix = config.prefix !== undefined ? config.prefix : '[OPTIMIZELY]' - } - - /** - * @param {LogLevel} level - * @param {string} message - * @memberof ConsoleLogger - */ - log(level: LogLevel, message: string) { - if (!this.shouldLog(level) || !this.logToConsole) { - return - } - - let logMessage: string = `${this.prefix} - ${this.getLogLevelName( - level, - )} ${this.getTime()} ${message}` - - this.consoleLog(level, [logMessage]) - } - - /** - * @param {LogLevel} level - * @memberof ConsoleLogger - */ - setLogLevel(level: LogLevel | string) { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - this.logLevel = LogLevel.ERROR - } else { - this.logLevel = level - } - } - - /** - * @returns {string} - * @memberof ConsoleLogger - */ - getTime(): string { - return new Date().toISOString() - } - - /** - * @private - * @param {LogLevel} targetLogLevel - * @returns {boolean} - * @memberof ConsoleLogger - */ - private shouldLog(targetLogLevel: LogLevel): boolean { - return targetLogLevel >= this.logLevel - } - - /** - * @private - * @param {LogLevel} logLevel - * @returns {string} - * @memberof ConsoleLogger - */ - private getLogLevelName(logLevel: LogLevel): string { - switch (logLevel) { - case LogLevel.DEBUG: - return 'DEBUG' - case LogLevel.INFO: - return 'INFO ' - case LogLevel.WARNING: - return 'WARN ' - case LogLevel.ERROR: - return 'ERROR' - default: - return 'NOTSET' - } - } - - /** - * @private - * @param {LogLevel} logLevel - * @param {string[]} logArguments - * @memberof ConsoleLogger - */ - private consoleLog(logLevel: LogLevel, logArguments: [string, ...string[]]) { - switch (logLevel) { - case LogLevel.DEBUG: - console.log.apply(console, logArguments) - break - case LogLevel.INFO: - console.info.apply(console, logArguments) - break - case LogLevel.WARNING: - console.warn.apply(console, logArguments) - break - case LogLevel.ERROR: - console.error.apply(console, logArguments) - break - default: - console.log.apply(console, logArguments) - } - } -} - -let globalLogLevel: LogLevel = LogLevel.NOTSET -let globalLogHandler: LogHandler | null = null - -class OptimizelyLogger implements LoggerFacade { - private messagePrefix: string = '' - - constructor(opts: { messagePrefix?: string } = {}) { - if (opts.messagePrefix) { - this.messagePrefix = opts.messagePrefix - } - } - - /** - * @param {(LogLevel | LogInputObject)} levelOrObj - * @param {string} [message] - * @memberof OptimizelyLogger - */ - log(level: LogLevel | string, message: string, ...splat: any[]): void { - this.internalLog(coerceLogLevel(level), { - message, - splat, - }) - } - - info(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.INFO, message, splat) - } - - debug(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.DEBUG, message, splat) - } - - warn(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.WARNING, message, splat) - } - - error(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.ERROR, message, splat) - } - - private format(data: LogData): string { - return `${this.messagePrefix ? this.messagePrefix + ': ' : ''}${sprintf( - data.message, - ...data.splat, - )}` - } - - private internalLog(level: LogLevel, data: LogData): void { - if (!globalLogHandler) { - return - } - - if (level < globalLogLevel) { - return - } - - globalLogHandler.log(level, this.format(data)) - - if (data.error && data.error instanceof Error) { - getErrorHandler().handleError(data.error) - } - } - - private namedLog(level: LogLevel, message: string | Error, splat: any[]): void { - let error: Error | undefined - - if (message instanceof Error) { - error = message - message = error.message - this.internalLog(level, { - error, - message, - splat, - }) - return - } - - if (splat.length === 0) { - this.internalLog(level, { - message, - splat, - }) - return - } - - const last = splat[splat.length - 1] - if (last instanceof Error) { - error = last - splat.splice(-1) - } - - this.internalLog(level, { message, error, splat }) - } -} - -let globalLogManager: LogManager = new DefaultLogManager() - -export function getLogger(name?: string): LoggerFacade { - return globalLogManager.getLogger(name) -} - -export function setLogHandler(logger: LogHandler | null) { - globalLogHandler = logger -} - -export function setLogLevel(level: LogLevel | string) { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - globalLogLevel = LogLevel.ERROR - } else { - globalLogLevel = level - } -} - -export function getLogLevel(): LogLevel { - return globalLogLevel -} - -/** - * Resets all global logger state to it's original - */ -export function resetLogger() { - globalLogManager = new DefaultLogManager() - globalLogLevel = LogLevel.NOTSET -} diff --git a/packages/logging/src/models.ts b/packages/logging/src/models.ts deleted file mode 100644 index cd3223932..000000000 --- a/packages/logging/src/models.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export enum LogLevel { - NOTSET = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - ERROR = 4, -} - -export interface LoggerFacade { - log(level: LogLevel | string, message: string, ...splat: any[]): void - - info(message: string | Error, ...splat: any[]): void - - debug(message: string | Error, ...splat: any[]): void - - warn(message: string | Error, ...splat: any[]): void - - error(message: string | Error, ...splat: any[]): void -} - -export interface LogManager { - getLogger(name?: string): LoggerFacade -} - -export interface LogHandler { - log(level: LogLevel, message: string, ...splat: any[]): void -} diff --git a/packages/logging/tsconfig.json b/packages/logging/tsconfig.json deleted file mode 100644 index 2ba8d7164..000000000 --- a/packages/logging/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src", - ], - "exclude": [ - "./lib", - "./src/**/*.spec.ts" - ] -} \ No newline at end of file diff --git a/packages/optimizely-sdk/README.md b/packages/optimizely-sdk/README.md index e614af651..9400ca4da 100644 --- a/packages/optimizely-sdk/README.md +++ b/packages/optimizely-sdk/README.md @@ -118,7 +118,6 @@ This repository houses the main Javascript SDK and its supporting packages. | Package | Version | Docs | Description | | - | - | - | - | | [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | (Consolidated*) Logging Manager for Optimizely SDK | [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages > \* Consolidated packages have been copied over and included as modules within the main `@optimizely/optimizely-sdk` package to avoid requiring maintaining and utilizing multiple de-coupled dependencies. (Related PRs [#749](https://github.com/optimizely/javascript-sdk/pull/749), [#755](https://github.com/optimizely/javascript-sdk/pull/755/files), [#761](https://github.com/optimizely/javascript-sdk/pull/761), [#781](https://github.com/optimizely/javascript-sdk/pull/781)) From cf0aa8ce20db9bcaedf72e4487b5d79d6ffddd2c Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 16 Aug 2023 16:36:07 +0600 Subject: [PATCH 033/200] [FSSDK-8219] removed util subpackage (#849) --- .github/workflows/javascript.yml | 23 +- README.md | 11 - packages/optimizely-sdk/README.md | 12 - packages/utils/.gitignore | 2 - packages/utils/CHANGELOG.md | 37 - packages/utils/LICENSE | 202 - packages/utils/README.md | 9 - packages/utils/__tests__/utils.spec.ts | 123 - packages/utils/jest.config.js | 17 - packages/utils/package-lock.json | 5342 ------------------------ packages/utils/package.json | 49 - packages/utils/src/index.ts | 157 - packages/utils/tsconfig.json | 13 - 13 files changed, 1 insertion(+), 5996 deletions(-) delete mode 100644 packages/utils/.gitignore delete mode 100644 packages/utils/CHANGELOG.md delete mode 100644 packages/utils/LICENSE delete mode 100644 packages/utils/README.md delete mode 100644 packages/utils/__tests__/utils.spec.ts delete mode 100644 packages/utils/jest.config.js delete mode 100644 packages/utils/package-lock.json delete mode 100644 packages/utils/package.json delete mode 100644 packages/utils/src/index.ts delete mode 100644 packages/utils/tsconfig.json diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 22059ccf2..de4a4a8d8 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -104,25 +104,4 @@ jobs: path-to-lcov: ./packages/optimizely-sdk/coverage/lcov.info parallel-finished: true base-path: ./packages/optimizely-sdk - - test_sub_packages: - runs-on: ubuntu-latest - strategy: - matrix: - package: [ 'packages/utils' ] - steps: - - uses: actions/checkout@v3 - - name: Move to package ${{ matrix.package }} - run: | - cd ${{ matrix.package }} - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: 14 - cache: 'npm' - cache-dependency-path: ${{ matrix.package }}/package-lock.json - - name: Test sub packages - working-directory: ./${{ matrix.package }} - run: | - npm install - npm test + \ No newline at end of file diff --git a/README.md b/README.md index 1865fd289..ae9e17689 100644 --- a/README.md +++ b/README.md @@ -111,17 +111,6 @@ Using `deno` (no installation required): import optimizely from "npm:@optimizely/optimizely-sdk" ``` -### Packages - -This repository is a monorepo. It houses the main Javascript SDK and its supporting packages. - -| Package | Version | Docs | Description | -| - | - | - | - | -| [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages - -> \* Consolidated packages have been copied over and included as modules within the main `@optimizely/optimizely-sdk` package to avoid requiring maintaining and utilizing multiple de-coupled dependencies. (Related PRs [#749](https://github.com/optimizely/javascript-sdk/pull/749), [#755](https://github.com/optimizely/javascript-sdk/pull/755/files), [#761](https://github.com/optimizely/javascript-sdk/pull/761), [#781](https://github.com/optimizely/javascript-sdk/pull/781)) - ## Use the JavaScript SDK (Browser) See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. diff --git a/packages/optimizely-sdk/README.md b/packages/optimizely-sdk/README.md index 9400ca4da..5e65ad0db 100644 --- a/packages/optimizely-sdk/README.md +++ b/packages/optimizely-sdk/README.md @@ -110,18 +110,6 @@ Using `deno` (no installation required): ```javascript import optimizely from "npm:@optimizely/optimizely-sdk" ``` - -### Packages - -This repository houses the main Javascript SDK and its supporting packages. - -| Package | Version | Docs | Description | -| - | - | - | - | -| [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | (Primary Package) The Optimizely JavaScript SDK | -| [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | (Consolidated*) Utility functions for Optimizely packages - -> \* Consolidated packages have been copied over and included as modules within the main `@optimizely/optimizely-sdk` package to avoid requiring maintaining and utilizing multiple de-coupled dependencies. (Related PRs [#749](https://github.com/optimizely/javascript-sdk/pull/749), [#755](https://github.com/optimizely/javascript-sdk/pull/755/files), [#761](https://github.com/optimizely/javascript-sdk/pull/761), [#781](https://github.com/optimizely/javascript-sdk/pull/781)) - ## Use the JavaScript SDK (Browser) See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. diff --git a/packages/utils/.gitignore b/packages/utils/.gitignore deleted file mode 100644 index d4a125901..000000000 --- a/packages/utils/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lib/ -doc/ diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md deleted file mode 100644 index 24407c8aa..000000000 --- a/packages/utils/CHANGELOG.md +++ /dev/null @@ -1,37 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.4.0] - July 27, 2020 - -- Removed React Native async storage implementation from utils ([#536](https://github.com/optimizely/javascript-sdk/pull/536)) - -## [0.3.2] - June 15, 2020 - -### Bug Fixes -- Wrap `uuid.v4` to disallow passing extra arguments and prevent dependency on `uuid` package types ([#509](https://github.com/optimizely/javascript-sdk/pull/509)) - -## [0.3.1] - June 12, 2020 - -### Bug Fixes -- Fix exports of `PersistentKeyValueCache` and `ReactNativeAsyncStorageCache` to be named, not default ([#506](https://github.com/optimizely/javascript-sdk/pull/506)) - -## [0.3.0] - June 11, 2020 - -### New Features -- Added `PersistentKeyValueCache` interface and its implementation for React Native under `ReactNativeAsyncStorageCache`. - -## [0.2.0] - August 7, 2019 - -### New Features -- Added `objectEntries` -- Added `NOTIFICATION_TYPES` and `NotificationCenter` - -## [0.1.0] - March 1, 2019 - -Initial release diff --git a/packages/utils/LICENSE b/packages/utils/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/utils/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/utils/README.md b/packages/utils/README.md deleted file mode 100644 index d9bb67bbc..000000000 --- a/packages/utils/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# `@optimizely/js-sdk-utils` - -A collection of utility functions shared between components of the Javascript SDK. - -### To test - -``` -npm test -``` \ No newline at end of file diff --git a/packages/utils/__tests__/utils.spec.ts b/packages/utils/__tests__/utils.spec.ts deleted file mode 100644 index 123b2805b..000000000 --- a/packages/utils/__tests__/utils.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -/// <reference types="jest" /> -import { isValidEnum, groupBy, objectEntries, objectValues, find, keyBy, sprintf } from '../src' - -describe('utils', () => { - describe('isValidEnum', () => { - enum myEnum { - FOO = 0, - BAR = 1, - } - - it('should return false when not valid', () => { - expect(isValidEnum(myEnum, 2)).toBe(false) - }) - - it('should return true when valid', () => { - expect(isValidEnum(myEnum, 1)).toBe(true) - expect(isValidEnum(myEnum, myEnum.FOO)).toBe(true) - }) - }) - - describe('groupBy', () => { - it('should group values by some key function', () => { - const input = [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - { firstName: 'james', lastName: 'foxy' }, - ] - const result = groupBy(input, item => item.firstName) - - expect(result).toEqual([ - [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - ], - [{ firstName: 'james', lastName: 'foxy' }], - ]) - }) - }) - - describe('objectEntries', () => { - it('should return object entries', () => { - expect(objectEntries({ foo: 'bar', bar: 123 })).toEqual([['foo', 'bar'], ['bar', 123]]) - }) - }) - - describe('objectValues', () => { - it('should return object values', () => { - expect(objectValues({ foo: 'bar', bar: 123 })).toEqual(['bar', 123]) - }) - // TODO test for enumerable properties only - }) - - describe('find', () => { - it('should return the value if found in an array', () => { - const input = [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - { firstName: 'james', lastName: 'foxy' }, - ] - - expect(find(input, item => item.firstName === 'jordan')).toEqual({ - firstName: 'jordan', - lastName: 'foo', - }) - }) - - it('should return undefined if NOT found in an array', () => { - const input = [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - { firstName: 'james', lastName: 'foxy' }, - ] - - expect(find(input, item => item.firstName === 'joe')).toBeUndefined() - }) - }) - - describe('keyBy', () => { - it('return an object with keys generated from the key function', () => { - const input = [ - { key: 'foo', firstName: 'jordan', lastName: 'foo' }, - { key: 'bar', firstName: 'jordan', lastName: 'bar' }, - { key: 'baz', firstName: 'james', lastName: 'foxy' }, - ] - - expect(keyBy(input, item => item.key)).toEqual({ - foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, - bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, - baz: { key: 'baz', firstName: 'james', lastName: 'foxy' }, - }) - }) - }) - - describe('sprintf', () => { - it('sprintf(msg)', () => { - expect(sprintf('this is my message')).toBe('this is my message') - }) - - it('sprintf(msg, arg1)', () => { - expect(sprintf('hi %s', 'jordan')).toBe('hi jordan') - }) - - it('sprintf(msg, arg1, arg2)', () => { - expect(sprintf('hi %s its %s', 'jordan', 'jon')).toBe('hi jordan its jon') - }) - - it('should print undefined if an argument is missing', () => { - expect(sprintf('hi %s its %s', 'jordan')).toBe('hi jordan its undefined') - }) - - it('should evaluate a function', () => { - expect(sprintf('hi %s its %s', 'jordan', () => 'a function')).toBe('hi jordan its a function') - }) - - it('should work with numbers', () => { - expect(sprintf('hi %s', 123)).toBe('hi 123') - }) - - it('should not error when passed an object', () => { - expect(sprintf('hi %s', { foo: 'bar' })).toBe('hi [object Object]') - }) - }) -}) diff --git a/packages/utils/jest.config.js b/packages/utils/jest.config.js deleted file mode 100644 index 391afe8eb..000000000 --- a/packages/utils/jest.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - // "roots": [ - // "./src" - // ], - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], -} \ No newline at end of file diff --git a/packages/utils/package-lock.json b/packages/utils/package-lock.json deleted file mode 100644 index 1cb04e1d9..000000000 --- a/packages/utils/package-lock.json +++ /dev/null @@ -1,5342 +0,0 @@ -{ - "name": "@optimizely/js-sdk-utils", - "version": "0.4.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "@types/node": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.4.tgz", - "integrity": "sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==", - "dev": true - }, - "@types/uuid": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz", - "integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - } - } - }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha512-Yisb7ew0ZEyDtRYQ+b+26o9KbiYPFxwcsxKzbssigzRRMJ9LpExPVUg6Fos7eP7yP3q7///tzze4nm4lTptPBw==", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", - "dev": true - }, - "array.prototype.reduce": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", - "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", - "integrity": "sha512-N0MlMjZtahXK0yb0K3V9hWPrq5e7tThbghvDr0k3X75UuOOqwsWW6mk8XHD2QvEC0Ca9dLIfTgNU36TeJD6Hnw==", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha512-C4Aq+GaAj83pRQ0EFgTvw5YO6T3Qz2KGrNRwIj9mSoNHVvdZY4KO2uA6HNtNXCw993iSZnckY1aLW8nOi8i4+w==", - "dev": true - }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha512-AdfWwc0PYvDtwr009yyVNh72Ev68os7SsPmOFVX7zSA+STXuk5CV2iMVazZU01bEoHCSwTkgv4E4HOOcODPkPg==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true - } - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha512-IS4lTgp57lUcpXzyCaiUQcRZBxZAkzl+jNXrMUXZjdnr2yujpKUMG9OYeYL29i6fL66ihypvVJ/MeX0B+9pWOg==", - "dev": true, - "requires": { - "rsvp": "^3.3.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true - }, - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha512-Dn2eAftOqXhNXs5f/Xjn7QTZ6kDYkx7u0EXQInN1oyYwsZysu11q7oTtaKcbzLxZRJiDHa8VmwpWmb4lY5FqgA==", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "exec-sh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", - "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, - "requires": { - "merge": "^1.2.0" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "expect": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", - "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^23.6.0", - "jest-get-type": "^22.1.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha512-UxowFKnAFIwtmSxgKjWAVgjE3Fk7MQJT0ZIyl0NwIFZTrx4913rLaonGJ84V+x/2+w/pe4ULHRns+GZPs1TVuw==", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha512-95jJZX6O/gdekidH2usRBr9WdRw4LU56CttPstXFxvG0r3QUE9eaIdz2p2Y7zrm6jxz7SjByAo1AtzwGlRvfOg==", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, - "jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", - "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", - "dev": true, - "requires": { - "import-local": "^1.0.0", - "jest-cli": "^23.6.0" - }, - "dependencies": { - "jest-cli": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", - "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.3.1", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-source-maps": "^1.2.4", - "jest-changed-files": "^23.4.2", - "jest-config": "^23.6.0", - "jest-environment-jsdom": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve-dependencies": "^23.6.0", - "jest-runner": "^23.6.0", - "jest-runtime": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "jest-watcher": "^23.4.0", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "node-notifier": "^5.2.1", - "prompts": "^0.1.9", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^1.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", - "which": "^1.2.12", - "yargs": "^11.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", - "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", - "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^23.6.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^23.4.0", - "jest-environment-node": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^23.6.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "pretty-format": "^23.6.0" - } - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-docblock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", - "integrity": "sha512-CB8MdScYLkzQ0Q/I4FYlt2UBkG9tFzi+ngSPVhSBB70nifaC+5iWz6GEfa/lB4T2KCqGy+DLzi1v34r9R1XzuA==", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", - "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "pretty-format": "^23.6.0" - } - }, - "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha512-UIXe32cMl/+DtyNHC15X+aFZMh04wx7PjWFBfz+nwoLgsIN2loKoNiKGSzUhMW/fVwbHrk8Qopglb7V4XB4EfQ==", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", - "integrity": "sha512-bk8qScgIfkb+EdwJ0JZ9xGvN7N3m6Qok73G8hi6tzvNadpe4kOxxuGmK2cJzAM3tPC/HBulzrOeNHEvaThQFrQ==", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "jest-haste-map": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", - "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", - "dev": true, - "requires": { - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", - "invariant": "^2.2.4", - "jest-docblock": "^23.2.0", - "jest-serializer": "^23.0.1", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "sane": "^2.0.0" - } - }, - "jest-jasmine2": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", - "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", - "dev": true, - "requires": { - "babel-traverse": "^6.0.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^23.6.0", - "is-generator-fn": "^1.0.0", - "jest-diff": "^23.6.0", - "jest-each": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "pretty-format": "^23.6.0" - } - }, - "jest-leak-detector": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", - "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", - "dev": true, - "requires": { - "pretty-format": "^23.6.0" - } - }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha512-Tjqy7T8jHhPgV4Gsi+pKMMfaz3uP5DPtMGnm8RWNWUHIk2igqxQ3/9rud3JkINCvZDGqlpJVuFGIDXbltG4xLA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha512-lz+Rf6dwRNDVowuGCXm93ib8hMyPntl1GGVt9PuZfBAmTjP5yKYgK14IASiEjs7XoMo4i/R7+dkrJY3eESwTJg==", - "dev": true - }, - "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha512-pNilf1tXhv5z0qjJy2Hl6Ar6dsi+XX2zpCAuzxRs4qoputI0Bm9rU7pa2ErrFTfiHYe8VboTR7WATPZXqzpQ/g==", - "dev": true - }, - "jest-resolve": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", - "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "realpath-native": "^1.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", - "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", - "dev": true, - "requires": { - "jest-regex-util": "^23.3.0", - "jest-snapshot": "^23.6.0" - } - }, - "jest-runner": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", - "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-docblock": "^23.2.0", - "jest-haste-map": "^23.6.0", - "jest-jasmine2": "^23.6.0", - "jest-leak-detector": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-runtime": "^23.6.0", - "jest-util": "^23.4.0", - "jest-worker": "^23.2.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", - "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-plugin-istanbul": "^4.1.6", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "exit": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^11.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - } - } - }, - "jest-serializer": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", - "integrity": "sha512-l6cPuiGEQI72H4+qMePF62E+URkZscnAqdHBYHkMrhKJOwU08AHvGmftXdosUzfCGhh/Ih4Xk1VgxnJSwrvQvQ==", - "dev": true - }, - "jest-snapshot": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", - "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", - "dev": true, - "requires": { - "babel-types": "^6.0.0", - "chalk": "^2.0.1", - "jest-diff": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-resolve": "^23.6.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^23.6.0", - "semver": "^5.5.0" - } - }, - "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha512-OS1/0QSbbMF9N93MxF1hUmK93EF3NGQGbbaTBZZk95aytWtWmzxsFWwt/UXIIkfHbPCK1fXTrPklbL+ohuFFOA==", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", - "mkdirp": "^0.5.1", - "slash": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-watcher": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", - "integrity": "sha512-BZGZYXnte/vazfnmkD4ERByi2O2mW+C++W8Sb7dvOnwcSccvCKNQgmcz1L+9hxVD7HWtqymPctIY7v5ZbQGNyg==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "string-length": "^2.0.0" - } - }, - "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha512-zx0uwPCDxToGfYyQiSHh7T/sKIxQFnQqT6Uug7Y/L7PzEkFITPaufjQe6yaf1OXSnGvKC5Fwol1hIym0zDzyvw==", - "dev": true, - "requires": { - "merge-stream": "^1.0.1" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "dev": true - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha512-e6RM36aegd4f+r8BZCcYXlO2P3H6xbUM6ktL2Xmf45GAOit9bI4z6/3VU7JwllVO1L7u0UDSg/EhzQ5lmMLolA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "nan": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", - "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-notifier": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", - "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true - }, - "nwsapi": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", - "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "requires": { - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", - "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", - "dev": true, - "requires": { - "array.prototype.reduce": "^1.0.4", - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - } - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "prompts": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", - "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", - "dev": true, - "requires": { - "kleur": "^2.0.1", - "sisteransi": "^0.1.1" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "dev": true, - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, - "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", - "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", - "integrity": "sha512-OuZwD1QJ2R9Dbnhd7Ur8zzD8l+oADp9npyxK63Q9nZ4AjhB2QwDQcQlD8iuUsGm5AZZqtEuCaJvK1rxGRxyQ1Q==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "capture-exit": "^1.2.0", - "exec-sh": "^0.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.3", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sisteransi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", - "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", - "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", - "dev": true - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha512-Qka42GGrS8Mm3SZ+7cH8UXiIWI867/b/Z/feQSpQx/rbfB8UGknGEZVaUQMOUVj+soY6NpWAxily63HI1OckVQ==", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - } - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "test-exclude": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", - "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^2.3.11", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha512-wCVxLDcFxw7ujDxaeJC6nfl2XfHJNYs8yUYJnvMgtPEFlttP9tHSfRUv2vBe6C4hkVFPWoP1P6ZccbYjmSEkKA==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "dev": true - }, - "ts-jest": { - "version": "23.10.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz", - "integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" - }, - "dependencies": { - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "uglify-js": { - "version": "3.16.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.3.tgz", - "integrity": "sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw==", - "dev": true, - "optional": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "util.promisify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", - "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "for-each": "^0.3.3", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.1" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha512-oUcoHFG3UF2pBlHcMORAojsN09BfqSfWYWlR3eSSjUFR7eBEx53WT2HX/vZeVTTIVCGShcazb+t6IcBRCNXqvA==", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - } - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", - "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "yargs": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", - "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha512-CswCfdOgCr4MMsT1GzbEJ7Z2uYudWyrGX8Bgh/0eyCzj/DXWdKq6a/ADufkzI1WAOIW6jYaXJvRyLhDO0kfqBw==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } -} diff --git a/packages/utils/package.json b/packages/utils/package.json deleted file mode 100644 index 92bcaaf1b..000000000 --- a/packages/utils/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@optimizely/js-sdk-utils", - "version": "0.4.0", - "description": "Optimizely Full Stack Utils", - "author": "jordangarcia <jordan@optimizely.com>", - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/utils", - "license": "Apache-2.0", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "directories": { - "lib": "lib", - "test": "test" - }, - "files": [ - "lib" - ], - "scripts": { - "clean": "rm -rf lib", - "prebuild": "npm run clean", - "build": "tsc", - "test": "jest", - "prepare": "npm run build", - "prepublishOnly": "npm test" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/utils" - }, - "keywords": [ - "optimizely" - ], - "bugs": { - "url": "https://github.com/optimizely/javascript-sdk/issues" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "uuid": "^3.3.2" - }, - "devDependencies": { - "@types/jest": "^23.3.12", - "@types/uuid": "^3.4.4", - "jest": "^23.6.0", - "ts-jest": "^23.10.5", - "typescript": "^4.7.4" - } -} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts deleted file mode 100644 index cd7dec342..000000000 --- a/packages/utils/src/index.ts +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { v4 } from 'uuid'; - -export function generateUUID(): string { - return v4() -} - -export type Omit<T, K> = Pick<T, Exclude<keyof T, K>> - -export function getTimestamp(): number { - return new Date().getTime() -} - -/** - * Validates a value is a valid TypeScript enum - * - * @export - * @param {object} enumToCheck - * @param {*} value - * @returns {boolean} - */ -export function isValidEnum(enumToCheck: { [key: string]: any }, value: any): boolean { - let found = false - - const keys = Object.keys(enumToCheck) - for (let index = 0; index < keys.length; index++) { - if (value === enumToCheck[keys[index]]) { - found = true - break - } - } - return found -} - -export function groupBy<K>(arr: K[], grouperFn: (item: K) => string): Array<K[]> { - const grouper: { [key: string]: K[] } = {} - - arr.forEach(item => { - const key = grouperFn(item) - grouper[key] = grouper[key] || [] - grouper[key].push(item) - }) - - return objectValues(grouper) -} - -export function objectValues<K>(obj: { [key: string]: K }): K[] { - return Object.keys(obj).map(key => obj[key]) -} - -export function objectEntries<K>(obj: { [key: string]: K }): [string, K][] { - return Object.keys(obj).map(key => [key, obj[key]]) -} - -export function find<K>(arr: K[], cond: (arg: K) => boolean): K | undefined { - let found - - for (let item of arr) { - if (cond(item)) { - found = item - break - } - } - - return found -} - -export function keyBy<K>(arr: K[], keyByFn: (item: K) => string): { [key: string]: K } { - let map: { [key: string]: K } = {} - arr.forEach(item => { - const key = keyByFn(item) - map[key] = item - }) - return map -} - -export function sprintf(format: string, ...args: any[]): string { - var i = 0 - return format.replace(/%s/g, function() { - const arg = args[i++] - const type = typeof arg - if (type === 'function') { - return arg() - } else if (type === 'string') { - return arg - } else { - return String(arg) - } - }) -} -/* - * Notification types for use with NotificationCenter - * Format is EVENT: <list of parameters to callback> - * - * SDK consumers can use these to register callbacks with the notification center. - * - * @deprecated since 3.1.0 - * ACTIVATE: An impression event will be sent to Optimizely - * Callbacks will receive an object argument with the following properties: - * - experiment {Object} - * - userId {string} - * - attributes {Object|undefined} - * - variation {Object} - * - logEvent {Object} - * - * DECISION: A decision is made in the system. i.e. user activation, - * feature access or feature-variable value retrieval - * Callbacks will receive an object argument with the following properties: - * - type {string} - * - userId {string} - * - attributes {Object|undefined} - * - decisionInfo {Object|undefined} - * - * LOG_EVENT: A batch of events, which could contain impressions and/or conversions, - * will be sent to Optimizely - * Callbacks will receive an object argument with the following properties: - * - url {string} - * - httpVerb {string} - * - params {Object} - * - * OPTIMIZELY_CONFIG_UPDATE: This Optimizely instance has been updated with a new - * config - * - * TRACK: A conversion event will be sent to Optimizely - * Callbacks will receive the an object argument with the following properties: - * - eventKey {string} - * - userId {string} - * - attributes {Object|undefined} - * - eventTags {Object|undefined} - * - logEvent {Object} - * - */ -export enum NOTIFICATION_TYPES { - ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', - DECISION = 'DECISION:type, userId, attributes, decisionInfo', - LOG_EVENT = 'LOG_EVENT:logEvent', - OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE', - TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event', -} - -export interface NotificationCenter { - sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData?: any): void -} diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json deleted file mode 100644 index 408b0e9a6..000000000 --- a/packages/utils/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src" - ], - "exclude": [ - "./lib", - "./src/**/*.spec.ts" - ] -} \ No newline at end of file From 59c5a086eca015ed9a467a0a898b34a268504a8f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 16 Aug 2023 19:30:46 +0600 Subject: [PATCH 034/200] [FSSDK-8219] consolidate tsconfig.json (#850) --- packages/optimizely-sdk/tsconfig.json | 15 +++++-- tsconfig.json | 63 --------------------------- 2 files changed, 12 insertions(+), 66 deletions(-) delete mode 100644 tsconfig.json diff --git a/packages/optimizely-sdk/tsconfig.json b/packages/optimizely-sdk/tsconfig.json index 5e19876a5..cd3b58451 100644 --- a/packages/optimizely-sdk/tsconfig.json +++ b/packages/optimizely-sdk/tsconfig.json @@ -1,6 +1,17 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { + "target": "es5", + "module": "esnext", + "lib": [ + "es2015", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, "baseUrl": "./", "paths": { "*": [ @@ -9,8 +20,6 @@ }, "resolveJsonModule": true, "allowJs": true, - "declaration": true, - "module": "esnext", "outDir": "./dist", "sourceMap": true, "skipLibCheck": true, diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 1073836e9..000000000 --- a/tsconfig.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "compilerOptions": { - /* Basic Options */ - "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, - "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, - "lib": [ - "es2015", - "dom" - ] /* Specify library files to be included in the compilation. */, - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true /* Generates corresponding '.d.ts' file. */, - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true /* Import emit helpers from 'tslib'. */, - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, - // "strictNullChecks": true /* Enable strict null checks. */, - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - }, - "exclude": ["node_modules", "src/**/*.spec.ts"] -} From 91826b08a83fc708f6727d342e60c02cf9800783 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 17 Aug 2023 13:43:00 +0600 Subject: [PATCH 035/200] [FSSDK-8219] moved optimizely-sdk package to repository root (#851) --- .devcontainer/devcontainer.json | 2 +- .../.eslintignore => .eslintignore | 0 .../.eslintrc.js => .eslintrc.js | 0 .github/workflows/javascript.yml | 23 +- .gitignore | 2 +- .prettierrc | 6 +- .../CHANGELOG.md => CHANGELOG.md | 0 README.md | 25 +- .../async-storage-event-processor.ts | 0 .../async-storage.ts | 0 .../@react-native-community/netinfo.ts | 0 .../jest.config.js => jest.config.js | 0 .../karma.base.conf.js => karma.base.conf.js | 0 .../karma.bs.conf.js => karma.bs.conf.js | 0 ...s.conf.js => karma.local_chrome.bs.conf.js | 0 ....conf.js => karma.local_chrome.umd.conf.js | 0 .../karma.umd.conf.js => karma.umd.conf.js | 0 .../core/audience_evaluator/index.tests.js | 0 .../core/audience_evaluator/index.ts | 0 .../index.tests.js | 0 .../odp_segment_condition_evaluator/index.ts | 0 .../lib => lib}/core/bucketer/index.tests.js | 0 .../lib => lib}/core/bucketer/index.ts | 0 .../condition_tree_evaluator/index.tests.js | 0 .../core/condition_tree_evaluator/index.ts | 0 .../index.tests.js | 0 .../index.ts | 0 .../lib => lib}/core/decision/index.tests.js | 0 .../lib => lib}/core/decision/index.ts | 0 .../core/decision_service/index.tests.js | 0 .../core/decision_service/index.ts | 0 .../core/event_builder/build_event_v1.ts | 0 .../core/event_builder/event_helpers.tests.js | 0 .../core/event_builder/event_helpers.ts | 0 .../core/event_builder/index.tests.js | 0 .../lib => lib}/core/event_builder/index.ts | 0 .../core/notification_center/index.tests.js | 0 .../core/notification_center/index.ts | 0 .../notification_registry.tests.ts | 0 .../notification_registry.ts | 0 .../lib => lib}/core/odp/odp_config.ts | 0 .../lib => lib}/core/odp/odp_event.ts | 0 .../core/odp/odp_event_api_manager.ts | 0 .../lib => lib}/core/odp/odp_event_manager.ts | 0 .../lib => lib}/core/odp/odp_manager.ts | 0 .../core/odp/odp_response_schema.ts | 0 .../core/odp/odp_segment_api_manager.ts | 0 .../core/odp/odp_segment_manager.ts | 0 .../lib => lib}/core/odp/odp_types.ts | 0 .../lib => lib}/core/odp/odp_utils.ts | 0 .../core/odp/optimizely_segment_option.ts | 0 .../core/optimizely_config/index.tests.js | 0 .../core/optimizely_config/index.ts | 0 .../core/project_config/index.tests.js | 0 .../lib => lib}/core/project_config/index.ts | 0 .../project_config_manager.tests.js | 0 .../project_config/project_config_manager.ts | 0 .../project_config/project_config_schema.ts | 0 .../lib => lib}/export_types.ts | 0 .../lib => lib}/index.browser.tests.js | 0 .../lib => lib}/index.browser.ts | 0 .../lib => lib}/index.browser.umdtests.js | 0 .../lib => lib}/index.lite.tests.js | 0 .../optimizely-sdk/lib => lib}/index.lite.ts | 0 .../lib => lib}/index.node.tests.js | 0 .../optimizely-sdk/lib => lib}/index.node.ts | 0 .../lib => lib}/index.react_native.ts | 0 .../datafile-manager/backoffController.ts | 0 .../browserDatafileManager.ts | 0 .../datafile-manager/browserRequest.ts | 0 .../modules/datafile-manager/config.ts | 0 .../datafile-manager/datafileManager.ts | 0 .../modules/datafile-manager/eventEmitter.ts | 0 .../modules/datafile-manager/http.ts | 0 .../httpPollingDatafileManager.ts | 0 .../modules/datafile-manager/index.browser.ts | 0 .../modules/datafile-manager/index.node.ts | 0 .../datafile-manager/index.react_native.ts | 0 .../datafile-manager/nodeDatafileManager.ts | 0 .../modules/datafile-manager/nodeRequest.ts | 0 .../persistentKeyValueCache.ts | 0 .../reactNativeAsyncStorageCache.ts | 0 .../reactNativeDatafileManager.ts | 0 .../event_processor/eventDispatcher.ts | 0 .../modules/event_processor/eventProcessor.ts | 0 .../modules/event_processor/eventQueue.ts | 0 .../modules/event_processor/events.ts | 0 .../event_processor/index.react_native.ts | 0 .../modules/event_processor/index.ts | 0 .../modules/event_processor/managed.ts | 0 .../pendingEventsDispatcher.ts | 0 .../event_processor/pendingEventsStore.ts | 0 .../persistentKeyValueCache.ts | 0 .../reactNativeAsyncStorageCache.ts | 0 .../event_processor/reactNativeEventsStore.ts | 0 .../modules/event_processor/requestTracker.ts | 0 .../modules/event_processor/synchronizer.ts | 0 .../event_processor/v1/buildEventV1.ts | 0 .../v1/v1EventProcessor.react_native.ts | 0 .../event_processor/v1/v1EventProcessor.ts | 0 .../modules/logging/errorHandler.ts | 0 .../lib => lib}/modules/logging/index.ts | 0 .../lib => lib}/modules/logging/logger.ts | 0 .../lib => lib}/modules/logging/models.ts | 0 .../lib => lib}/optimizely/index.tests.js | 0 .../lib => lib}/optimizely/index.ts | 0 .../lib => lib}/optimizely_decision/index.ts | 0 .../optimizely_user_context/index.tests.js | 0 .../optimizely_user_context/index.ts | 0 .../browser_http_polling_datafile_manager.ts | 0 .../http_polling_datafile_manager.tests.js | 0 .../http_polling_datafile_manager.ts | 0 .../no_op_datafile_manager.tests.js | 0 .../no_op_datafile_manager.ts | 0 ...ct_native_http_polling_datafile_manager.ts | 0 .../plugins/error_handler/index.tests.js | 0 .../plugins/error_handler/index.ts | 0 .../event_dispatcher/index.browser.tests.js | 0 .../plugins/event_dispatcher/index.browser.ts | 0 .../event_dispatcher/index.node.tests.js | 0 .../plugins/event_dispatcher/index.node.ts | 0 .../plugins/event_dispatcher/no_op.ts | 0 .../forwarding_event_processor.tests.js | 0 .../forwarding_event_processor.ts | 0 .../event_processor/index.react_native.ts | 0 .../plugins/event_processor/index.ts | 0 .../browserAsyncStorageCache.ts | 0 .../persistentKeyValueCache.ts | 0 .../reactNativeAsyncStorageCache.ts | 0 .../logger/index.react_native.tests.js | 0 .../plugins/logger/index.react_native.ts | 0 .../lib => lib}/plugins/logger/index.tests.js | 0 .../lib => lib}/plugins/logger/index.ts | 0 .../odp/event_api_manager/index.browser.ts | 0 .../odp/event_api_manager/index.node.ts | 0 .../odp/event_manager/index.browser.ts | 0 .../plugins/odp/event_manager/index.node.ts | 0 .../plugins/odp_manager/index.browser.ts | 0 .../plugins/odp_manager/index.node.ts | 0 .../lib => lib}/plugins/vuid_manager/index.ts | 0 .../lib => lib}/shared_types.ts | 0 .../tests/exit_on_unhandled_rejection.js | 0 .../lib => lib}/tests/test_data.js | 0 .../utils/attributes_validator/index.tests.js | 0 .../utils/attributes_validator/index.ts | 0 .../utils/config_validator/index.tests.js | 0 .../utils/config_validator/index.ts | 0 .../lib => lib}/utils/enums/index.ts | 0 .../index.tests.js | 0 .../event_processor_config_validator/index.ts | 0 .../utils/event_tag_utils/index.tests.js | 0 .../utils/event_tag_utils/index.ts | 0 .../utils/event_tags_validator/index.tests.js | 0 .../utils/event_tags_validator/index.ts | 0 .../lib => lib}/utils/fns/index.tests.js | 0 .../lib => lib}/utils/fns/index.ts | 0 .../browser_request_handler.ts | 0 .../utils/http_request_handler/http.ts | 0 .../node_request_handler.ts | 0 .../json_schema_validator/index.tests.js | 0 .../utils/json_schema_validator/index.ts | 0 .../utils/local_storage/tryLocalStorage.ts | 0 .../utils/lru_cache/browser_lru_cache.ts | 0 .../utils/lru_cache/cache_element.tests.ts | 0 .../utils/lru_cache/cache_element.ts | 0 .../lib => lib}/utils/lru_cache/index.ts | 0 .../utils/lru_cache/lru_cache.tests.ts | 0 .../lib => lib}/utils/lru_cache/lru_cache.ts | 0 .../utils/lru_cache/server_lru_cache.ts | 0 .../utils/semantic_version/index.tests.js | 0 .../utils/semantic_version/index.ts | 0 .../string_value_validator/index.tests.js | 0 .../utils/string_value_validator/index.ts | 0 .../index.tests.js | 0 .../user_profile_service_validator/index.ts | 0 package-lock.json | 11831 ++++++++++------ .../package.json => package.json | 5 +- packages/optimizely-sdk/.prettierrc | 10 - packages/optimizely-sdk/README.md | 252 - packages/optimizely-sdk/package-lock.json | 8872 ------------ .../rollup.config.js => rollup.config.js | 0 .../optimizely-sdk/srcclr.yml => srcclr.yml | 0 .../tests => tests}/backoffController.spec.ts | 0 .../browserAsyncStorageCache.spec.ts | 0 .../browserDatafileManager.spec.ts | 0 .../tests => tests}/browserRequest.spec.ts | 0 .../browserRequestHandler.spec.ts | 0 .../tests => tests}/buildEventV1.spec.ts | 0 .../tests => tests}/eventEmitter.spec.ts | 0 .../tests => tests}/eventQueue.spec.ts | 0 .../httpPollingDatafileManager.spec.ts | 0 .../httpPollingDatafileManagerPolling.spec.ts | 0 .../index.react_native.spec.ts | 0 .../tests => tests}/jsconfig.json | 0 .../tests => tests}/logger.spec.ts | 0 .../nodeDatafileManager.spec.ts | 0 .../tests => tests}/nodeRequest.spec.ts | 0 .../nodeRequestHandler.spec.ts | 0 .../odpEventApiManager.spec.ts | 0 .../tests => tests}/odpEventManager.spec.ts | 0 .../odpManager.browser.spec.ts | 0 .../tests => tests}/odpManager.spec.ts | 0 .../odpSegmentApiManager.spec.ts | 0 .../tests => tests}/odpSegmentManager.spec.ts | 0 .../pendingEventsDispatcher.spec.ts | 0 .../pendingEventsStore.spec.ts | 0 .../reactNativeAsyncStorageCache.spec.ts | 0 .../reactNativeEventsStore.spec.ts | 0 .../tests => tests}/requestTracker.spec.ts | 0 .../tests => tests}/testUtils.ts | 0 .../tests => tests}/utils.spec.ts | 0 .../v1EventProcessor.react_native.spec.ts | 0 .../tests => tests}/v1EventProcessor.spec.ts | 0 .../tests => tests}/vuidManager.spec.ts | 0 .../tsconfig.json => tsconfig.json | 0 .../tsconfig.spec.json => tsconfig.spec.json | 0 .../typings => typings}/murmurhash.d.ts | 0 217 files changed, 7844 insertions(+), 13184 deletions(-) rename packages/optimizely-sdk/.eslintignore => .eslintignore (100%) rename packages/optimizely-sdk/.eslintrc.js => .eslintrc.js (100%) rename packages/optimizely-sdk/CHANGELOG.md => CHANGELOG.md (100%) rename {packages/optimizely-sdk/__mocks__ => __mocks__}/@react-native-async-storage/async-storage-event-processor.ts (100%) rename {packages/optimizely-sdk/__mocks__ => __mocks__}/@react-native-async-storage/async-storage.ts (100%) rename {packages/optimizely-sdk/__mocks__ => __mocks__}/@react-native-community/netinfo.ts (100%) rename packages/optimizely-sdk/jest.config.js => jest.config.js (100%) rename packages/optimizely-sdk/karma.base.conf.js => karma.base.conf.js (100%) rename packages/optimizely-sdk/karma.bs.conf.js => karma.bs.conf.js (100%) rename packages/optimizely-sdk/karma.local_chrome.bs.conf.js => karma.local_chrome.bs.conf.js (100%) rename packages/optimizely-sdk/karma.local_chrome.umd.conf.js => karma.local_chrome.umd.conf.js (100%) rename packages/optimizely-sdk/karma.umd.conf.js => karma.umd.conf.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/audience_evaluator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/audience_evaluator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/audience_evaluator/odp_segment_condition_evaluator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/bucketer/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/bucketer/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/condition_tree_evaluator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/condition_tree_evaluator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/custom_attribute_condition_evaluator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/custom_attribute_condition_evaluator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/decision/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/decision/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/decision_service/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/decision_service/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/event_builder/build_event_v1.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/event_builder/event_helpers.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/event_builder/event_helpers.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/event_builder/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/event_builder/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/notification_center/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/notification_center/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/notification_center/notification_registry.tests.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/notification_center/notification_registry.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_config.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_event.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_event_api_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_event_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_response_schema.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_segment_api_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_segment_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_types.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/odp_utils.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/odp/optimizely_segment_option.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/optimizely_config/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/optimizely_config/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/project_config/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/project_config/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/project_config/project_config_manager.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/core/project_config/project_config_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/core/project_config/project_config_schema.ts (100%) rename {packages/optimizely-sdk/lib => lib}/export_types.ts (100%) rename {packages/optimizely-sdk/lib => lib}/index.browser.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/index.browser.ts (100%) rename {packages/optimizely-sdk/lib => lib}/index.browser.umdtests.js (100%) rename {packages/optimizely-sdk/lib => lib}/index.lite.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/index.lite.ts (100%) rename {packages/optimizely-sdk/lib => lib}/index.node.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/index.node.ts (100%) rename {packages/optimizely-sdk/lib => lib}/index.react_native.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/backoffController.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/browserDatafileManager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/browserRequest.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/config.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/datafileManager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/eventEmitter.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/http.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/httpPollingDatafileManager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/index.browser.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/index.node.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/index.react_native.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/nodeDatafileManager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/nodeRequest.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/persistentKeyValueCache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/reactNativeAsyncStorageCache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/datafile-manager/reactNativeDatafileManager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/eventDispatcher.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/eventProcessor.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/eventQueue.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/events.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/index.react_native.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/managed.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/pendingEventsDispatcher.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/pendingEventsStore.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/persistentKeyValueCache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/reactNativeAsyncStorageCache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/reactNativeEventsStore.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/requestTracker.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/synchronizer.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/v1/buildEventV1.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/v1/v1EventProcessor.react_native.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/event_processor/v1/v1EventProcessor.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/logging/errorHandler.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/logging/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/logging/logger.ts (100%) rename {packages/optimizely-sdk/lib => lib}/modules/logging/models.ts (100%) rename {packages/optimizely-sdk/lib => lib}/optimizely/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/optimizely/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/optimizely_decision/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/optimizely_user_context/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/optimizely_user_context/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/datafile_manager/browser_http_polling_datafile_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/datafile_manager/http_polling_datafile_manager.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/datafile_manager/http_polling_datafile_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/datafile_manager/no_op_datafile_manager.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/datafile_manager/no_op_datafile_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/error_handler/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/error_handler/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_dispatcher/index.browser.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_dispatcher/index.browser.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_dispatcher/index.node.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_dispatcher/index.node.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_dispatcher/no_op.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_processor/forwarding_event_processor.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_processor/forwarding_event_processor.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_processor/index.react_native.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/event_processor/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/key_value_cache/browserAsyncStorageCache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/key_value_cache/persistentKeyValueCache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/key_value_cache/reactNativeAsyncStorageCache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/logger/index.react_native.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/logger/index.react_native.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/logger/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/logger/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/odp/event_api_manager/index.browser.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/odp/event_api_manager/index.node.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/odp/event_manager/index.browser.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/odp/event_manager/index.node.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/odp_manager/index.browser.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/odp_manager/index.node.ts (100%) rename {packages/optimizely-sdk/lib => lib}/plugins/vuid_manager/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/shared_types.ts (100%) rename {packages/optimizely-sdk/lib => lib}/tests/exit_on_unhandled_rejection.js (100%) rename {packages/optimizely-sdk/lib => lib}/tests/test_data.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/attributes_validator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/attributes_validator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/config_validator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/config_validator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/enums/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/event_processor_config_validator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/event_processor_config_validator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/event_tag_utils/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/event_tag_utils/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/event_tags_validator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/event_tags_validator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/fns/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/fns/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/http_request_handler/browser_request_handler.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/http_request_handler/http.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/http_request_handler/node_request_handler.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/json_schema_validator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/json_schema_validator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/local_storage/tryLocalStorage.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/lru_cache/browser_lru_cache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/lru_cache/cache_element.tests.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/lru_cache/cache_element.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/lru_cache/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/lru_cache/lru_cache.tests.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/lru_cache/lru_cache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/lru_cache/server_lru_cache.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/semantic_version/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/semantic_version/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/string_value_validator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/string_value_validator/index.ts (100%) rename {packages/optimizely-sdk/lib => lib}/utils/user_profile_service_validator/index.tests.js (100%) rename {packages/optimizely-sdk/lib => lib}/utils/user_profile_service_validator/index.ts (100%) rename packages/optimizely-sdk/package.json => package.json (96%) delete mode 100644 packages/optimizely-sdk/.prettierrc delete mode 100644 packages/optimizely-sdk/README.md delete mode 100644 packages/optimizely-sdk/package-lock.json rename packages/optimizely-sdk/rollup.config.js => rollup.config.js (100%) rename packages/optimizely-sdk/srcclr.yml => srcclr.yml (100%) rename {packages/optimizely-sdk/tests => tests}/backoffController.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/browserAsyncStorageCache.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/browserDatafileManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/browserRequest.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/browserRequestHandler.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/buildEventV1.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/eventEmitter.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/eventQueue.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/httpPollingDatafileManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/httpPollingDatafileManagerPolling.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/index.react_native.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/jsconfig.json (100%) rename {packages/optimizely-sdk/tests => tests}/logger.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/nodeDatafileManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/nodeRequest.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/nodeRequestHandler.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/odpEventApiManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/odpEventManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/odpManager.browser.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/odpManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/odpSegmentApiManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/odpSegmentManager.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/pendingEventsDispatcher.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/pendingEventsStore.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/reactNativeAsyncStorageCache.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/reactNativeEventsStore.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/requestTracker.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/testUtils.ts (100%) rename {packages/optimizely-sdk/tests => tests}/utils.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/v1EventProcessor.react_native.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/v1EventProcessor.spec.ts (100%) rename {packages/optimizely-sdk/tests => tests}/vuidManager.spec.ts (100%) rename packages/optimizely-sdk/tsconfig.json => tsconfig.json (100%) rename packages/optimizely-sdk/tsconfig.spec.json => tsconfig.spec.json (100%) rename {packages/optimizely-sdk/typings => typings}/murmurhash.d.ts (100%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index acd068bff..bda7bb833 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ "name": "Javascript SDK", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-18-bookworm", - "postCreateCommand": "cd /workspaces/javascript-sdk/packages/optimizely-sdk && npm install -g npm && npm install", + "postCreateCommand": "cd /workspaces/javascript-sdk && npm install -g npm && npm install", "customizations": { "vscode": { diff --git a/packages/optimizely-sdk/.eslintignore b/.eslintignore similarity index 100% rename from packages/optimizely-sdk/.eslintignore rename to .eslintignore diff --git a/packages/optimizely-sdk/.eslintrc.js b/.eslintrc.js similarity index 100% rename from packages/optimizely-sdk/.eslintrc.js rename to .eslintrc.js diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index de4a4a8d8..33d5b6c28 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -18,10 +18,10 @@ jobs: uses: actions/setup-node@v3 with: node-version: 14 - cache-dependency-path: packages/optimizely-sdk/package-lock.json + cache-dependency-path: ./package-lock.json cache: 'npm' - name: Run linting - working-directory: ./packages/optimizely-sdk + working-directory: . run: | npm install npm run lint @@ -47,17 +47,14 @@ jobs: BROWSER_STACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} steps: - uses: actions/checkout@v3 - - name: Move to package - run: | - cd packages/optimizely-sdk - name: Set up Node uses: actions/setup-node@v3 with: node-version: 14 cache: 'npm' - cache-dependency-path: packages/optimizely-sdk/package-lock.json + cache-dependency-path: ./package-lock.json - name: Cross-browser and umd unit tests - working-directory: ./packages/optimizely-sdk + working-directory: . run: | npm install npm run test-ci @@ -74,9 +71,9 @@ jobs: with: node-version: ${{ matrix.node }} cache: 'npm' - cache-dependency-path: packages/optimizely-sdk/package-lock.json + cache-dependency-path: ./package-lock.json - name: Unit tests - working-directory: ./packages/optimizely-sdk + working-directory: . run: | npm install npm run coveralls @@ -84,11 +81,11 @@ jobs: uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./packages/optimizely-sdk/coverage/lcov.info + path-to-lcov: ./coverage/lcov.info flag-name: run-${{ matrix.node }} # This is a parallel build so need this parallel: true - base-path: ./packages/optimizely-sdk + base-path: . # As testing against multiple versions need this to # finish the parallel build @@ -101,7 +98,7 @@ jobs: uses: coverallsapp/github-action@master with: github-token: ${{ secrets.github_token }} - path-to-lcov: ./packages/optimizely-sdk/coverage/lcov.info + path-to-lcov: ./coverage/lcov.info parallel-finished: true - base-path: ./packages/optimizely-sdk + base-path: . \ No newline at end of file diff --git a/.gitignore b/.gitignore index ad0e62dca..5431dd3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules/ -/packages/*/optimizely-*.tgz +optimizely-*.tgz npm-debug.log lerna-debug.log diff --git a/.prettierrc b/.prettierrc index 95d49510c..62301e2e3 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,10 +1,10 @@ { - "printWidth": 89, + "printWidth": 120, "tabWidth": 2, "useTabs": false, - "semi": false, + "semi": true, "singleQuote": true, - "trailingComma": "all", + "trailingComma": "es5", "bracketSpacing": true, "jsxBracketSameLine": false } diff --git a/packages/optimizely-sdk/CHANGELOG.md b/CHANGELOG.md similarity index 100% rename from packages/optimizely-sdk/CHANGELOG.md rename to CHANGELOG.md diff --git a/README.md b/README.md index ae9e17689..5e65ad0db 100644 --- a/README.md +++ b/README.md @@ -16,22 +16,22 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat ## Get Started -For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. +> For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. -For **React** applications, refer to the [React SDK developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-sdk). +> For **React** applications, refer to the [React SDK developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-sdk). -For **React Native** applications, refer to the [JavaScript (React Native) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-native-sdk). +> For **React Native** applications, refer to the [JavaScript (React Native) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-native-sdk). -For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk). +> For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk). -For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: -- [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) -- [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) -- [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) -- [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) -- [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) - -Note: These starter kits use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. +> For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: +> - [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) +> - [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) +> - [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) +> - [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) +> - [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) +> +> Note: These starter kits use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. ### Prerequisites @@ -110,7 +110,6 @@ Using `deno` (no installation required): ```javascript import optimizely from "npm:@optimizely/optimizely-sdk" ``` - ## Use the JavaScript SDK (Browser) See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. diff --git a/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage-event-processor.ts b/__mocks__/@react-native-async-storage/async-storage-event-processor.ts similarity index 100% rename from packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage-event-processor.ts rename to __mocks__/@react-native-async-storage/async-storage-event-processor.ts diff --git a/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts b/__mocks__/@react-native-async-storage/async-storage.ts similarity index 100% rename from packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts rename to __mocks__/@react-native-async-storage/async-storage.ts diff --git a/packages/optimizely-sdk/__mocks__/@react-native-community/netinfo.ts b/__mocks__/@react-native-community/netinfo.ts similarity index 100% rename from packages/optimizely-sdk/__mocks__/@react-native-community/netinfo.ts rename to __mocks__/@react-native-community/netinfo.ts diff --git a/packages/optimizely-sdk/jest.config.js b/jest.config.js similarity index 100% rename from packages/optimizely-sdk/jest.config.js rename to jest.config.js diff --git a/packages/optimizely-sdk/karma.base.conf.js b/karma.base.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.base.conf.js rename to karma.base.conf.js diff --git a/packages/optimizely-sdk/karma.bs.conf.js b/karma.bs.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.bs.conf.js rename to karma.bs.conf.js diff --git a/packages/optimizely-sdk/karma.local_chrome.bs.conf.js b/karma.local_chrome.bs.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.local_chrome.bs.conf.js rename to karma.local_chrome.bs.conf.js diff --git a/packages/optimizely-sdk/karma.local_chrome.umd.conf.js b/karma.local_chrome.umd.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.local_chrome.umd.conf.js rename to karma.local_chrome.umd.conf.js diff --git a/packages/optimizely-sdk/karma.umd.conf.js b/karma.umd.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.umd.conf.js rename to karma.umd.conf.js diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/lib/core/audience_evaluator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js rename to lib/core/audience_evaluator/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/audience_evaluator/index.ts rename to lib/core/audience_evaluator/index.ts diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js rename to lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts rename to lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/bucketer/index.tests.js rename to lib/core/bucketer/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/bucketer/index.ts rename to lib/core/bucketer/index.ts diff --git a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.tests.js b/lib/core/condition_tree_evaluator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.tests.js rename to lib/core/condition_tree_evaluator/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts b/lib/core/condition_tree_evaluator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts rename to lib/core/condition_tree_evaluator/index.ts diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js b/lib/core/custom_attribute_condition_evaluator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js rename to lib/core/custom_attribute_condition_evaluator/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts rename to lib/core/custom_attribute_condition_evaluator/index.ts diff --git a/packages/optimizely-sdk/lib/core/decision/index.tests.js b/lib/core/decision/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/decision/index.tests.js rename to lib/core/decision/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/decision/index.ts b/lib/core/decision/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/decision/index.ts rename to lib/core/decision/index.ts diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/decision_service/index.tests.js rename to lib/core/decision_service/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/decision_service/index.ts rename to lib/core/decision_service/index.ts diff --git a/packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts b/lib/core/event_builder/build_event_v1.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts rename to lib/core/event_builder/build_event_v1.ts diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.tests.js b/lib/core/event_builder/event_helpers.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/event_builder/event_helpers.tests.js rename to lib/core/event_builder/event_helpers.tests.js diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts b/lib/core/event_builder/event_helpers.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts rename to lib/core/event_builder/event_helpers.ts diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.tests.js b/lib/core/event_builder/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/event_builder/index.tests.js rename to lib/core/event_builder/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.ts b/lib/core/event_builder/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/event_builder/index.ts rename to lib/core/event_builder/index.ts diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.tests.js b/lib/core/notification_center/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/notification_center/index.tests.js rename to lib/core/notification_center/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.ts b/lib/core/notification_center/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/notification_center/index.ts rename to lib/core/notification_center/index.ts diff --git a/packages/optimizely-sdk/lib/core/notification_center/notification_registry.tests.ts b/lib/core/notification_center/notification_registry.tests.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/notification_center/notification_registry.tests.ts rename to lib/core/notification_center/notification_registry.tests.ts diff --git a/packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts b/lib/core/notification_center/notification_registry.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/notification_center/notification_registry.ts rename to lib/core/notification_center/notification_registry.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_config.ts rename to lib/core/odp/odp_config.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event.ts b/lib/core/odp/odp_event.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_event.ts rename to lib/core/odp/odp_event.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts b/lib/core/odp/odp_event_api_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts rename to lib/core/odp/odp_event_api_manager.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts rename to lib/core/odp/odp_event_manager.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_manager.ts rename to lib/core/odp/odp_manager.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_response_schema.ts b/lib/core/odp/odp_response_schema.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_response_schema.ts rename to lib/core/odp/odp_response_schema.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts b/lib/core/odp/odp_segment_api_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_segment_api_manager.ts rename to lib/core/odp/odp_segment_api_manager.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts b/lib/core/odp/odp_segment_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_segment_manager.ts rename to lib/core/odp/odp_segment_manager.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_types.ts b/lib/core/odp/odp_types.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_types.ts rename to lib/core/odp/odp_types.ts diff --git a/packages/optimizely-sdk/lib/core/odp/odp_utils.ts b/lib/core/odp/odp_utils.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/odp_utils.ts rename to lib/core/odp/odp_utils.ts diff --git a/packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts b/lib/core/odp/optimizely_segment_option.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/odp/optimizely_segment_option.ts rename to lib/core/odp/optimizely_segment_option.ts diff --git a/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js b/lib/core/optimizely_config/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js rename to lib/core/optimizely_config/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/optimizely_config/index.ts b/lib/core/optimizely_config/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/optimizely_config/index.ts rename to lib/core/optimizely_config/index.ts diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/lib/core/project_config/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/project_config/index.tests.js rename to lib/core/project_config/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/project_config/index.ts b/lib/core/project_config/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/project_config/index.ts rename to lib/core/project_config/index.ts diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js b/lib/core/project_config/project_config_manager.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js rename to lib/core/project_config/project_config_manager.tests.js diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts b/lib/core/project_config/project_config_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts rename to lib/core/project_config/project_config_manager.ts diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_schema.ts b/lib/core/project_config/project_config_schema.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/project_config/project_config_schema.ts rename to lib/core/project_config/project_config_schema.ts diff --git a/packages/optimizely-sdk/lib/export_types.ts b/lib/export_types.ts similarity index 100% rename from packages/optimizely-sdk/lib/export_types.ts rename to lib/export_types.ts diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/lib/index.browser.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/index.browser.tests.js rename to lib/index.browser.tests.js diff --git a/packages/optimizely-sdk/lib/index.browser.ts b/lib/index.browser.ts similarity index 100% rename from packages/optimizely-sdk/lib/index.browser.ts rename to lib/index.browser.ts diff --git a/packages/optimizely-sdk/lib/index.browser.umdtests.js b/lib/index.browser.umdtests.js similarity index 100% rename from packages/optimizely-sdk/lib/index.browser.umdtests.js rename to lib/index.browser.umdtests.js diff --git a/packages/optimizely-sdk/lib/index.lite.tests.js b/lib/index.lite.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/index.lite.tests.js rename to lib/index.lite.tests.js diff --git a/packages/optimizely-sdk/lib/index.lite.ts b/lib/index.lite.ts similarity index 100% rename from packages/optimizely-sdk/lib/index.lite.ts rename to lib/index.lite.ts diff --git a/packages/optimizely-sdk/lib/index.node.tests.js b/lib/index.node.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/index.node.tests.js rename to lib/index.node.tests.js diff --git a/packages/optimizely-sdk/lib/index.node.ts b/lib/index.node.ts similarity index 100% rename from packages/optimizely-sdk/lib/index.node.ts rename to lib/index.node.ts diff --git a/packages/optimizely-sdk/lib/index.react_native.ts b/lib/index.react_native.ts similarity index 100% rename from packages/optimizely-sdk/lib/index.react_native.ts rename to lib/index.react_native.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/backoffController.ts b/lib/modules/datafile-manager/backoffController.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/backoffController.ts rename to lib/modules/datafile-manager/backoffController.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/browserDatafileManager.ts b/lib/modules/datafile-manager/browserDatafileManager.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/browserDatafileManager.ts rename to lib/modules/datafile-manager/browserDatafileManager.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/browserRequest.ts b/lib/modules/datafile-manager/browserRequest.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/browserRequest.ts rename to lib/modules/datafile-manager/browserRequest.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/config.ts b/lib/modules/datafile-manager/config.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/config.ts rename to lib/modules/datafile-manager/config.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts b/lib/modules/datafile-manager/datafileManager.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts rename to lib/modules/datafile-manager/datafileManager.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/eventEmitter.ts b/lib/modules/datafile-manager/eventEmitter.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/eventEmitter.ts rename to lib/modules/datafile-manager/eventEmitter.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/http.ts b/lib/modules/datafile-manager/http.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/http.ts rename to lib/modules/datafile-manager/http.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/lib/modules/datafile-manager/httpPollingDatafileManager.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts rename to lib/modules/datafile-manager/httpPollingDatafileManager.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/index.browser.ts b/lib/modules/datafile-manager/index.browser.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/index.browser.ts rename to lib/modules/datafile-manager/index.browser.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/index.node.ts b/lib/modules/datafile-manager/index.node.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/index.node.ts rename to lib/modules/datafile-manager/index.node.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/index.react_native.ts b/lib/modules/datafile-manager/index.react_native.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/index.react_native.ts rename to lib/modules/datafile-manager/index.react_native.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/nodeDatafileManager.ts b/lib/modules/datafile-manager/nodeDatafileManager.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/nodeDatafileManager.ts rename to lib/modules/datafile-manager/nodeDatafileManager.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/nodeRequest.ts b/lib/modules/datafile-manager/nodeRequest.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/nodeRequest.ts rename to lib/modules/datafile-manager/nodeRequest.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/persistentKeyValueCache.ts b/lib/modules/datafile-manager/persistentKeyValueCache.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/persistentKeyValueCache.ts rename to lib/modules/datafile-manager/persistentKeyValueCache.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts b/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts rename to lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeDatafileManager.ts b/lib/modules/datafile-manager/reactNativeDatafileManager.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeDatafileManager.ts rename to lib/modules/datafile-manager/reactNativeDatafileManager.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts b/lib/modules/event_processor/eventDispatcher.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts rename to lib/modules/event_processor/eventDispatcher.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts b/lib/modules/event_processor/eventProcessor.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts rename to lib/modules/event_processor/eventProcessor.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts b/lib/modules/event_processor/eventQueue.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts rename to lib/modules/event_processor/eventQueue.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/events.ts b/lib/modules/event_processor/events.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/events.ts rename to lib/modules/event_processor/events.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts b/lib/modules/event_processor/index.react_native.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts rename to lib/modules/event_processor/index.react_native.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/index.ts b/lib/modules/event_processor/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/index.ts rename to lib/modules/event_processor/index.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/managed.ts b/lib/modules/event_processor/managed.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/managed.ts rename to lib/modules/event_processor/managed.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts b/lib/modules/event_processor/pendingEventsDispatcher.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts rename to lib/modules/event_processor/pendingEventsDispatcher.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts b/lib/modules/event_processor/pendingEventsStore.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts rename to lib/modules/event_processor/pendingEventsStore.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts b/lib/modules/event_processor/persistentKeyValueCache.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts rename to lib/modules/event_processor/persistentKeyValueCache.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts b/lib/modules/event_processor/reactNativeAsyncStorageCache.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts rename to lib/modules/event_processor/reactNativeAsyncStorageCache.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts b/lib/modules/event_processor/reactNativeEventsStore.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts rename to lib/modules/event_processor/reactNativeEventsStore.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts b/lib/modules/event_processor/requestTracker.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts rename to lib/modules/event_processor/requestTracker.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts b/lib/modules/event_processor/synchronizer.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts rename to lib/modules/event_processor/synchronizer.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts b/lib/modules/event_processor/v1/buildEventV1.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts rename to lib/modules/event_processor/v1/buildEventV1.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts b/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts rename to lib/modules/event_processor/v1/v1EventProcessor.react_native.ts diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts b/lib/modules/event_processor/v1/v1EventProcessor.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts rename to lib/modules/event_processor/v1/v1EventProcessor.ts diff --git a/packages/optimizely-sdk/lib/modules/logging/errorHandler.ts b/lib/modules/logging/errorHandler.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/logging/errorHandler.ts rename to lib/modules/logging/errorHandler.ts diff --git a/packages/optimizely-sdk/lib/modules/logging/index.ts b/lib/modules/logging/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/logging/index.ts rename to lib/modules/logging/index.ts diff --git a/packages/optimizely-sdk/lib/modules/logging/logger.ts b/lib/modules/logging/logger.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/logging/logger.ts rename to lib/modules/logging/logger.ts diff --git a/packages/optimizely-sdk/lib/modules/logging/models.ts b/lib/modules/logging/models.ts similarity index 100% rename from packages/optimizely-sdk/lib/modules/logging/models.ts rename to lib/modules/logging/models.ts diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/optimizely/index.tests.js rename to lib/optimizely/index.tests.js diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/lib/optimizely/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/optimizely/index.ts rename to lib/optimizely/index.ts diff --git a/packages/optimizely-sdk/lib/optimizely_decision/index.ts b/lib/optimizely_decision/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/optimizely_decision/index.ts rename to lib/optimizely_decision/index.ts diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js rename to lib/optimizely_user_context/index.tests.js diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/optimizely_user_context/index.ts rename to lib/optimizely_user_context/index.ts diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts b/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts rename to lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js b/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js rename to lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts b/lib/plugins/datafile_manager/http_polling_datafile_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts rename to lib/plugins/datafile_manager/http_polling_datafile_manager.ts diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js b/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js rename to lib/plugins/datafile_manager/no_op_datafile_manager.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts b/lib/plugins/datafile_manager/no_op_datafile_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts rename to lib/plugins/datafile_manager/no_op_datafile_manager.ts diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts b/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts rename to lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts diff --git a/packages/optimizely-sdk/lib/plugins/error_handler/index.tests.js b/lib/plugins/error_handler/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/error_handler/index.tests.js rename to lib/plugins/error_handler/index.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/error_handler/index.ts b/lib/plugins/error_handler/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/error_handler/index.ts rename to lib/plugins/error_handler/index.ts diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.tests.js b/lib/plugins/event_dispatcher/index.browser.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.tests.js rename to lib/plugins/event_dispatcher/index.browser.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.ts b/lib/plugins/event_dispatcher/index.browser.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.ts rename to lib/plugins/event_dispatcher/index.browser.ts diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.tests.js b/lib/plugins/event_dispatcher/index.node.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.tests.js rename to lib/plugins/event_dispatcher/index.node.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.ts b/lib/plugins/event_dispatcher/index.node.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.ts rename to lib/plugins/event_dispatcher/index.node.ts diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/no_op.ts b/lib/plugins/event_dispatcher/no_op.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_dispatcher/no_op.ts rename to lib/plugins/event_dispatcher/no_op.ts diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.tests.js b/lib/plugins/event_processor/forwarding_event_processor.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.tests.js rename to lib/plugins/event_processor/forwarding_event_processor.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts b/lib/plugins/event_processor/forwarding_event_processor.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts rename to lib/plugins/event_processor/forwarding_event_processor.ts diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts b/lib/plugins/event_processor/index.react_native.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts rename to lib/plugins/event_processor/index.react_native.ts diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/index.ts b/lib/plugins/event_processor/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/event_processor/index.ts rename to lib/plugins/event_processor/index.ts diff --git a/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts b/lib/plugins/key_value_cache/browserAsyncStorageCache.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts rename to lib/plugins/key_value_cache/browserAsyncStorageCache.ts diff --git a/packages/optimizely-sdk/lib/plugins/key_value_cache/persistentKeyValueCache.ts b/lib/plugins/key_value_cache/persistentKeyValueCache.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/key_value_cache/persistentKeyValueCache.ts rename to lib/plugins/key_value_cache/persistentKeyValueCache.ts diff --git a/packages/optimizely-sdk/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts rename to lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.tests.js b/lib/plugins/logger/index.react_native.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/logger/index.react_native.tests.js rename to lib/plugins/logger/index.react_native.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts b/lib/plugins/logger/index.react_native.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts rename to lib/plugins/logger/index.react_native.ts diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.tests.js b/lib/plugins/logger/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/plugins/logger/index.tests.js rename to lib/plugins/logger/index.tests.js diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.ts b/lib/plugins/logger/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/logger/index.ts rename to lib/plugins/logger/index.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/plugins/odp/event_api_manager/index.browser.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.browser.ts rename to lib/plugins/odp/event_api_manager/index.browser.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts b/lib/plugins/odp/event_api_manager/index.node.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/event_api_manager/index.node.ts rename to lib/plugins/odp/event_api_manager/index.node.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts b/lib/plugins/odp/event_manager/index.browser.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/event_manager/index.browser.ts rename to lib/plugins/odp/event_manager/index.browser.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts b/lib/plugins/odp/event_manager/index.node.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp/event_manager/index.node.ts rename to lib/plugins/odp/event_manager/index.node.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp_manager/index.browser.ts rename to lib/plugins/odp_manager/index.browser.ts diff --git a/packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts b/lib/plugins/odp_manager/index.node.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/odp_manager/index.node.ts rename to lib/plugins/odp_manager/index.node.ts diff --git a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts b/lib/plugins/vuid_manager/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts rename to lib/plugins/vuid_manager/index.ts diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/lib/shared_types.ts similarity index 100% rename from packages/optimizely-sdk/lib/shared_types.ts rename to lib/shared_types.ts diff --git a/packages/optimizely-sdk/lib/tests/exit_on_unhandled_rejection.js b/lib/tests/exit_on_unhandled_rejection.js similarity index 100% rename from packages/optimizely-sdk/lib/tests/exit_on_unhandled_rejection.js rename to lib/tests/exit_on_unhandled_rejection.js diff --git a/packages/optimizely-sdk/lib/tests/test_data.js b/lib/tests/test_data.js similarity index 100% rename from packages/optimizely-sdk/lib/tests/test_data.js rename to lib/tests/test_data.js diff --git a/packages/optimizely-sdk/lib/utils/attributes_validator/index.tests.js b/lib/utils/attributes_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/attributes_validator/index.tests.js rename to lib/utils/attributes_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/attributes_validator/index.ts b/lib/utils/attributes_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/attributes_validator/index.ts rename to lib/utils/attributes_validator/index.ts diff --git a/packages/optimizely-sdk/lib/utils/config_validator/index.tests.js b/lib/utils/config_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/config_validator/index.tests.js rename to lib/utils/config_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/config_validator/index.ts rename to lib/utils/config_validator/index.ts diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/lib/utils/enums/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/enums/index.ts rename to lib/utils/enums/index.ts diff --git a/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.tests.js b/lib/utils/event_processor_config_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.tests.js rename to lib/utils/event_processor_config_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.ts b/lib/utils/event_processor_config_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.ts rename to lib/utils/event_processor_config_validator/index.ts diff --git a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.tests.js b/lib/utils/event_tag_utils/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/event_tag_utils/index.tests.js rename to lib/utils/event_tag_utils/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts rename to lib/utils/event_tag_utils/index.ts diff --git a/packages/optimizely-sdk/lib/utils/event_tags_validator/index.tests.js b/lib/utils/event_tags_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/event_tags_validator/index.tests.js rename to lib/utils/event_tags_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/event_tags_validator/index.ts b/lib/utils/event_tags_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/event_tags_validator/index.ts rename to lib/utils/event_tags_validator/index.ts diff --git a/packages/optimizely-sdk/lib/utils/fns/index.tests.js b/lib/utils/fns/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/fns/index.tests.js rename to lib/utils/fns/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/fns/index.ts b/lib/utils/fns/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/fns/index.ts rename to lib/utils/fns/index.ts diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/browser_request_handler.ts b/lib/utils/http_request_handler/browser_request_handler.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/http_request_handler/browser_request_handler.ts rename to lib/utils/http_request_handler/browser_request_handler.ts diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/http.ts b/lib/utils/http_request_handler/http.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/http_request_handler/http.ts rename to lib/utils/http_request_handler/http.ts diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts b/lib/utils/http_request_handler/node_request_handler.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts rename to lib/utils/http_request_handler/node_request_handler.ts diff --git a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.tests.js b/lib/utils/json_schema_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/json_schema_validator/index.tests.js rename to lib/utils/json_schema_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts b/lib/utils/json_schema_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts rename to lib/utils/json_schema_validator/index.ts diff --git a/packages/optimizely-sdk/lib/utils/local_storage/tryLocalStorage.ts b/lib/utils/local_storage/tryLocalStorage.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/local_storage/tryLocalStorage.ts rename to lib/utils/local_storage/tryLocalStorage.ts diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts b/lib/utils/lru_cache/browser_lru_cache.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/lru_cache/browser_lru_cache.ts rename to lib/utils/lru_cache/browser_lru_cache.ts diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/cache_element.tests.ts b/lib/utils/lru_cache/cache_element.tests.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/lru_cache/cache_element.tests.ts rename to lib/utils/lru_cache/cache_element.tests.ts diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/cache_element.ts b/lib/utils/lru_cache/cache_element.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/lru_cache/cache_element.ts rename to lib/utils/lru_cache/cache_element.ts diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/index.ts b/lib/utils/lru_cache/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/lru_cache/index.ts rename to lib/utils/lru_cache/index.ts diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.tests.ts b/lib/utils/lru_cache/lru_cache.tests.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.tests.ts rename to lib/utils/lru_cache/lru_cache.tests.ts diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts b/lib/utils/lru_cache/lru_cache.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/lru_cache/lru_cache.ts rename to lib/utils/lru_cache/lru_cache.ts diff --git a/packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts b/lib/utils/lru_cache/server_lru_cache.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/lru_cache/server_lru_cache.ts rename to lib/utils/lru_cache/server_lru_cache.ts diff --git a/packages/optimizely-sdk/lib/utils/semantic_version/index.tests.js b/lib/utils/semantic_version/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/semantic_version/index.tests.js rename to lib/utils/semantic_version/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/semantic_version/index.ts b/lib/utils/semantic_version/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/semantic_version/index.ts rename to lib/utils/semantic_version/index.ts diff --git a/packages/optimizely-sdk/lib/utils/string_value_validator/index.tests.js b/lib/utils/string_value_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/string_value_validator/index.tests.js rename to lib/utils/string_value_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/string_value_validator/index.ts b/lib/utils/string_value_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/string_value_validator/index.ts rename to lib/utils/string_value_validator/index.ts diff --git a/packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.tests.js b/lib/utils/user_profile_service_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.tests.js rename to lib/utils/user_profile_service_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.ts b/lib/utils/user_profile_service_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.ts rename to lib/utils/user_profile_service_validator/index.ts diff --git a/package-lock.json b/package-lock.json index 5bc8a6877..eb0b7d74f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2814 +1,6046 @@ { - "requires": true, + "name": "@optimizely/optimizely-sdk", + "version": "5.0.0-beta2", "lockfileVersion": 1, + "requires": true, "dependencies": { - "@lerna/add": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.2.0.tgz", - "integrity": "sha512-qGA7agAWcKlrXZR3FwFJXTr26Q2rqjOVMNhtm8uyawImqfdKp4WJXuGdioiWOSW20jMvzLIFhWZh5lCh0UyMBw==", - "requires": { - "@lerna/bootstrap": "^3.2.0", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/npm-conf": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "dedent": "^0.7.0", - "npm-package-arg": "^6.0.0", - "p-map": "^1.2.0", - "pacote": "^9.1.0", - "semver": "^5.5.0" - } - }, - "@lerna/batch-packages": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.1.2.tgz", - "integrity": "sha512-HAkpptrYeUVlBYbLScXgeCgk6BsNVXxDd53HVWgzzTWpXV4MHpbpeKrByyt7viXlNhW0w73jJbipb/QlFsHIhQ==", - "requires": { - "@lerna/package-graph": "^3.1.2", - "@lerna/validation-error": "^3.0.0", - "npmlog": "^4.1.2" - } - }, - "@lerna/bootstrap": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.2.0.tgz", - "integrity": "sha512-xh6dPpdzsAEWF7lqASaym5AThkmP3ArR7Q+P/tiPWCT+OT7QT5QI2IQAz1aAYEBQL3ACzpE6kq+VOGi0m+9bxw==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/has-npm-version": "^3.0.4", - "@lerna/npm-conf": "^3.0.0", - "@lerna/npm-install": "^3.0.0", - "@lerna/rimraf-dir": "^3.0.0", - "@lerna/run-lifecycle": "^3.2.0", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/symlink-binary": "^3.1.4", - "@lerna/symlink-dependencies": "^3.1.4", - "@lerna/validation-error": "^3.0.0", - "dedent": "^0.7.0", - "get-port": "^3.2.0", - "multimatch": "^2.1.0", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "p-finally": "^1.0.0", - "p-map": "^1.2.0", - "p-map-series": "^1.0.0", - "p-waterfall": "^1.0.0", - "read-package-tree": "^5.1.6", - "semver": "^5.5.0" - } - }, - "@lerna/changed": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.2.0.tgz", - "integrity": "sha512-R+vGzzXPN5s5lJT0v1zSTLw43O2ek2yekqCqvw7p9UFqgqYSbxUsyWXMdhku/mOIFWTc6DzrsOi+U7CX3TXmHg==", - "requires": { - "@lerna/collect-updates": "^3.1.0", - "@lerna/command": "^3.1.3", - "@lerna/listable": "^3.0.0", - "@lerna/output": "^3.0.0", - "@lerna/version": "^3.2.0" - } - }, - "@lerna/check-working-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.1.0.tgz", - "integrity": "sha512-ruy6s44IUcaPEa4JlDDDk6nbacMESUPRSb+dohzLJxfhXx1wFnEVF6L91TGxFP+C0lt5V6zd8rnJxkW/uZzNAA==", - "requires": { - "@lerna/describe-ref": "^3.1.0", - "@lerna/validation-error": "^3.0.0" - } - }, - "@lerna/child-process": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.0.0.tgz", - "integrity": "sha512-8vHRDkpGhzSaMsXgyXVgY80mUSC5WSkDmhWWA3bnB/n5FBK1gK8EKQUpHTk14SckwvUgEJzBd35gR5/XKGOgmQ==", - "requires": { - "chalk": "^2.3.1", - "execa": "^0.10.0", - "strong-log-transformer": "^1.0.6" - } - }, - "@lerna/clean": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.1.3.tgz", - "integrity": "sha512-XVdcIOjhudXlk5pTXjrpsnNLqeVi2rBu2oWzPH2GHrxWGBZBW8thGIFhQf09da/RbRT3uzBWXpUv+sbL2vbX3g==", - "requires": { - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/prompt": "^3.0.0", - "@lerna/rimraf-dir": "^3.0.0", - "p-map": "^1.2.0", - "p-map-series": "^1.0.0", - "p-waterfall": "^1.0.0" + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", + "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", + "dev": true + }, + "@babel/core": { + "version": "7.18.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", + "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.13", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.13", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.13", + "@babel/types": "^7.18.13", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "@lerna/cli": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/cli/-/cli-3.2.0.tgz", - "integrity": "sha512-JdbLyTxHqxUlrkI+Ke+ltXbtyA+MPu9zR6kg/n8Fl6uaez/2fZWtReXzYi8MgLxfUFa7+1OHWJv4eAMZlByJ+Q==", + "@babel/generator": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", + "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "dev": true, "requires": { - "@lerna/global-options": "^3.1.3", - "dedent": "^0.7.0", - "npmlog": "^4.1.2", - "yargs": "^12.0.1" + "@babel/types": "^7.19.0", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "requires": { - "xregexp": "4.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "yargs": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", - "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", - "requires": { - "cliui": "^4.0.0", - "decamelize": "^2.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" - } + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true } } }, - "@lerna/collect-updates": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.1.0.tgz", - "integrity": "sha512-zHxHRZOteTqcW9mAyLrmoWEKpfxgA3c6LJj4nauB2pM3MKyKNhg0gqiy2RHFu7EGivPki4Q1624I301iAXtUVA==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/describe-ref": "^3.1.0", - "minimatch": "^3.0.4", - "npmlog": "^4.1.2", - "slash": "^1.0.0" - } - }, - "@lerna/command": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.1.3.tgz", - "integrity": "sha512-ptaFNsfcTpxnSkaNrGgW3fRbWWVSVDUx4BpkjUUnRTgy9mwoykQWgQB3inhgTYHwW6e4PzO79F2hovfUMzHuzg==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/package-graph": "^3.1.2", - "@lerna/project": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "@lerna/write-log-file": "^3.0.0", - "dedent": "^0.7.0", - "execa": "^0.10.0", - "is-ci": "^1.0.10", - "lodash": "^4.17.5", - "npmlog": "^4.1.2" - } - }, - "@lerna/conventional-commits": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.0.2.tgz", - "integrity": "sha512-Cxd1eWXn3usADKXIUvYmTERx2+1N7oJD4Whz+FVu8kTfufsfTO7fYOan1RVkg86ukZbNDyS+iOxZ8DJ2JspS9g==", + "@babel/helper-compilation-targets": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", + "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", + "dev": true, "requires": { - "@lerna/validation-error": "^3.0.0", - "conventional-changelog-angular": "^1.6.6", - "conventional-changelog-core": "^2.0.5", - "conventional-recommended-bump": "^2.0.6", - "dedent": "^0.7.0", - "fs-extra": "^6.0.1", - "get-stream": "^3.0.0", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "semver": "^5.5.0" - } - }, - "@lerna/create": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.1.3.tgz", - "integrity": "sha512-CmXKCBc6AE3F9O6mMg4Y76cQ8eTCy3ksqDFKxVQMdYDtHKnTrH1s0l8sn3J1AiylqVDnvxhb3Rjyj+OWyzmFPQ==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/npm-conf": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "camelcase": "^4.1.0", - "dedent": "^0.7.0", - "fs-extra": "^6.0.1", - "globby": "^8.0.1", - "init-package-json": "^1.10.3", - "npm-package-arg": "^6.0.0", - "pify": "^3.0.0", - "semver": "^5.5.0", - "slash": "^1.0.0", - "validate-npm-package-license": "^3.0.3", - "validate-npm-package-name": "^3.0.0", - "whatwg-url": "^6.5.0" + "@babel/compat-data": "^7.19.1", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, - "@lerna/create-symlink": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.0.0.tgz", - "integrity": "sha512-Q9qAzGGqQtVzHWrCz+Md4SH0tW99DrgFJ68cnFqilOO6H3Y/y/H0gwHICqM9YxRwLs6GJdkzoqJATFShM7PKJA==", - "requires": { - "cmd-shim": "^2.0.2", - "fs-extra": "^6.0.1", - "npmlog": "^4.1.2" - } - }, - "@lerna/describe-ref": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.1.0.tgz", - "integrity": "sha512-0a7WFKDSmdEwwmEj+ZfhI7SkkG1CDcVhfW8VhPqr6gnCMY+ryt6iV/rR7ygb0eCDg8wEe9eQsiwbnrbXDLjIDw==", - "requires": { - "@lerna/child-process": "^3.0.0", - "npmlog": "^4.1.2" - } - }, - "@lerna/diff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.1.3.tgz", - "integrity": "sha512-30G74DxdQC4dR3U0yqh5mjcioLDUmSy1ntntdF3khvKV9oiMVzCSOO0oOlSwIdmohke+bQ//oF+oyl0Dy1TUTQ==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/validation-error": "^3.0.0", - "npmlog": "^4.1.2" - } + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true }, - "@lerna/exec": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.1.3.tgz", - "integrity": "sha512-r0yQj9Rza5a42Shts8rXYGU1/Va8hO2atk/TEZgrA1EcTwgZyAuXsuML5UWbC/eLgwEjVDmc3MUSj8O1JijBMQ==", + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0" + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" } }, - "@lerna/filter-options": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.1.2.tgz", - "integrity": "sha512-smbvSGK/eU+7PDKO4jbJ7XYO2XTfhnwPeOTuwSm1mlWS5dUGasYkhAuFzouFh60aZBvmW0e6APe0XYeofQNcCg==", + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, "requires": { - "@lerna/collect-updates": "^3.1.0", - "@lerna/filter-packages": "^3.0.0", - "dedent": "^0.7.0" + "@babel/types": "^7.18.6" } }, - "@lerna/filter-packages": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.0.0.tgz", - "integrity": "sha512-zwbY1J4uRjWRZ/FgYbtVkq7I3Nduwsg2V2HwLKSzwV2vPglfGqgovYOVkND6/xqe2BHwDX4IyA2+e7OJmLaLSA==", + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, "requires": { - "@lerna/validation-error": "^3.0.0", - "multimatch": "^2.1.0", - "npmlog": "^4.1.2" + "@babel/types": "^7.18.6" } }, - "@lerna/get-npm-exec-opts": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.0.0.tgz", - "integrity": "sha512-arcYUm+4xS8J3Palhl+5rRJXnZnFHsLFKHBxznkPIxjwGQeAEw7df38uHdVjEQ+HNeFmHnBgSqfbxl1VIw5DHg==", + "@babel/helper-module-transforms": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", + "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", + "dev": true, "requires": { - "npmlog": "^4.1.2" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" } }, - "@lerna/global-options": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/global-options/-/global-options-3.1.3.tgz", - "integrity": "sha512-LVeZU/Zgc0XkHdGMRYn+EmHfDmmYNwYRv3ta59iCVFXLVp7FRFWF7oB1ss/WRa9x/pYU0o6L8as/5DomLUGASA==" + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "dev": true }, - "@lerna/has-npm-version": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.0.4.tgz", - "integrity": "sha512-RisEWZBROi8corPb/PUIQqT+xGPLeriJ/n6VCeO6GROCO1fyYBX7kgFmVpFOytufWFkI04qBgLaUs+CEc8Yspg==", + "@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, "requires": { - "@lerna/child-process": "^3.0.0", - "semver": "^5.5.0" + "@babel/types": "^7.18.6" } }, - "@lerna/import": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.1.3.tgz", - "integrity": "sha512-+XV/EHXEHbyMmprz8zQa0VF0TZ+txRIrcF3Q/PsuZ4isVxawIbP1CmgE0yn0/1XSNJwGKsuPfGauRtnjUi2LJw==", + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/prompt": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "dedent": "^0.7.0", - "fs-extra": "^6.0.1", - "p-map-series": "^1.0.0" + "@babel/types": "^7.18.6" } }, - "@lerna/init": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.1.3.tgz", - "integrity": "sha512-c418p6fAfJ+b/tidB8/O/ABGLX7a5y5uJSWxH2/Mp7i5da/RD27XJ6E6818hGAbUbVQw04+XuXHtrWYlWLEJCw==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "fs-extra": "^6.0.1", - "p-map": "^1.2.0", - "write-json-file": "^2.3.0" - } + "@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "dev": true }, - "@lerna/link": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.1.4.tgz", - "integrity": "sha512-AAl4ctKtE6975zxdrsr16CAh/K4y0ZjjY9XDdD+zMxsSPELvRVG6M36O1A72AmKz5Nhh1l82bPrw7w54TbQ8hA==", - "requires": { - "@lerna/command": "^3.1.3", - "@lerna/package-graph": "^3.1.2", - "@lerna/symlink-dependencies": "^3.1.4", - "p-map": "^1.2.0", - "slash": "^1.0.0" - } + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true }, - "@lerna/list": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.1.3.tgz", - "integrity": "sha512-/ncX5Kj1ddLgZuBkjaoluJgcmAAm/L4/AymVNBgVrw6dOad0C0RB6oIcRAbxDennEQ25wSOFmuXRZHYHY9VYyg==", - "requires": { - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/listable": "^3.0.0", - "@lerna/output": "^3.0.0" - } + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true }, - "@lerna/listable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.0.0.tgz", - "integrity": "sha512-HX/9hyx1HLg2kpiKXIUc1EimlkK1T58aKQ7ovO7rQdTx9ForpefoMzyLnHE1n4XrUtEszcSWJIICJ/F898M6Ag==", + "@babel/helpers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "dev": true, "requires": { - "chalk": "^2.3.1", - "columnify": "^1.5.4" + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" } }, - "@lerna/log-packed": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.0.4.tgz", - "integrity": "sha512-vVQHgMagE2wnbxhNY9nFkdu+Cx2TsyWalkJfkxbNzmo6gOCrDsxCBDj9vTEV8Q+4aWx0C0Bsc0sB2Eb8y/+ofA==", + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, "requires": { - "byte-size": "^4.0.3", - "columnify": "^1.5.4", - "has-unicode": "^2.0.1", - "npmlog": "^4.1.2" + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" } }, - "@lerna/npm-conf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.0.0.tgz", - "integrity": "sha512-xXG7qt349t+xzaHTQELmIDjbq8Q49HOMR8Nx/gTDBkMl02Fno91LXFnA4A7ErPiyUSGqNSfLw+zgij0hgpeN7w==", - "requires": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" - } + "@babel/parser": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", + "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", + "dev": true }, - "@lerna/npm-dist-tag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.0.0.tgz", - "integrity": "sha512-ZOcfcsNJlCoVHvLOROdCTvqD3keG3TJ78Cu8sALsz8n0kEz2ga7tNy5wbQ67SGyY7+jq4YiBv5BwXjV+56Sv+A==", + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "npmlog": "^4.1.2" - } - }, - "@lerna/npm-install": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.0.0.tgz", - "integrity": "sha512-e0sspVUfzEKhqsRIxzWqZ/uMBHzZSzOa4HCeORErEZu+dmDoI145XYhqvCVn7EvbAb407FV2H9GVeoP0JeG8GQ==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "fs-extra": "^6.0.1", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "signal-exit": "^3.0.2", - "write-pkg": "^3.1.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@lerna/npm-publish": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.2.0.tgz", - "integrity": "sha512-x13EGrjZk9w8gCQAE44aKbeO1xhLizLJ4tKjzZmQqKEaUCugF4UU8ZRGshPMRFBdsHTEWh05dkKx2oPMoaf0dw==", + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "@lerna/has-npm-version": "^3.0.4", - "@lerna/log-packed": "^3.0.4", - "fs-extra": "^6.0.1", - "npmlog": "^4.1.2", - "p-map": "^1.2.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@lerna/npm-run-script": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.0.0.tgz", - "integrity": "sha512-Y1H4Myer1S7an33FDK0eqyR+95PujUePC/xJZKq/H50SaQNwBw7KMlxXxy6kXVEcQhmvQsER4Bw3msgqwwGYIw==", + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "npmlog": "^4.1.2" + "@babel/helper-plugin-utils": "^7.12.13" } }, - "@lerna/output": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/output/-/output-3.0.0.tgz", - "integrity": "sha512-EFxnSbO0zDEVKkTKpoCUAFcZjc3gn3DwPlyTDxbeqPU7neCfxP4rA4+0a6pcOfTlRS5kLBRMx79F2TRCaMM3DA==", + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "requires": { - "npmlog": "^4.1.2" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@lerna/package": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.0.0.tgz", - "integrity": "sha512-djzEJxzn212wS8d9znBnlXkeRlPL7GqeAYBykAmsuq51YGvaQK67Umh5ejdO0uxexF/4r7yRwgrlRHpQs8Rfqg==", + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "requires": { - "npm-package-arg": "^6.0.0", - "write-pkg": "^3.1.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@lerna/package-graph": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.1.2.tgz", - "integrity": "sha512-9wIWb49I1IJmyjPdEVZQ13IAi9biGfH/OZHOC04U2zXGA0GLiY+B3CAx6FQvqkZ8xEGfqzmXnv3LvZ0bQfc1aQ==", + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "requires": { - "@lerna/validation-error": "^3.0.0", - "npm-package-arg": "^6.0.0", - "semver": "^5.5.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@lerna/project": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.0.0.tgz", - "integrity": "sha512-XhDFVfqj79jG2Speggd15RpYaE8uiR25UKcQBDmumbmqvTS7xf2cvl2pq2UTvDafaJ0YwFF3xkxQZeZnFMwdkw==", + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "requires": { - "@lerna/package": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "cosmiconfig": "^5.0.2", - "dedent": "^0.7.0", - "dot-prop": "^4.2.0", - "glob-parent": "^3.1.0", - "globby": "^8.0.1", - "load-json-file": "^4.0.0", - "npmlog": "^4.1.2", - "p-map": "^1.2.0", - "resolve-from": "^4.0.0", - "write-json-file": "^2.3.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@lerna/prompt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.0.0.tgz", - "integrity": "sha512-EzvNexDTh//GlpOz68zRo16NdOIqWqiiXMs9tIxpELQubH+kUGKvBSiBrZ2Zyrfd8pQhIf+8qARtkCG+G7wzQQ==", + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "requires": { - "inquirer": "^5.1.0", - "npmlog": "^4.1.2" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@lerna/publish": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.2.1.tgz", - "integrity": "sha512-SnSBstK/G9qLt5rS56pihNacgsu3UgxXiCexWb57GGEp2eDguQ7rFzxVs4JMQQWmVG97EMJQxfFV54tW2sqtIw==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/check-working-tree": "^3.1.0", - "@lerna/child-process": "^3.0.0", - "@lerna/collect-updates": "^3.1.0", - "@lerna/command": "^3.1.3", - "@lerna/describe-ref": "^3.1.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "@lerna/npm-dist-tag": "^3.0.0", - "@lerna/npm-publish": "^3.2.0", - "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.0.0", - "@lerna/run-lifecycle": "^3.2.0", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "@lerna/version": "^3.2.0", - "fs-extra": "^6.0.1", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "p-finally": "^1.0.0", - "p-map": "^1.2.0", - "p-pipe": "^1.2.0", - "p-reduce": "^1.0.0", - "semver": "^5.5.0" - } - }, - "@lerna/resolve-symlink": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.0.0.tgz", - "integrity": "sha512-MqjW9e+QVXts5IK5dk1XnYx7JKb+g+tQkOnnpAxYWHjahf3rGJ7Ru8maWh8KoPE+nIHAekk4WcjpiA9nLKvkFQ==", + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "requires": { - "fs-extra": "^6.0.1", - "npmlog": "^4.1.2", - "read-cmd-shim": "^1.0.1" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@lerna/rimraf-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.0.0.tgz", - "integrity": "sha512-epvh/RGWSOYdrNgrizMcRq9VyCHkeY0LpIE4074r4ouKdYNhBT0LlpT0yMLvQgQKJkKRlqcfhJHvZeGHhXQyGg==", + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "requires": { - "@lerna/child-process": "^3.0.0", - "npmlog": "^4.1.2", - "path-exists": "^3.0.0", - "rimraf": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@lerna/run": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.1.3.tgz", - "integrity": "sha512-O26WdR+sQFSG2Fpc67nw+m8oVq3R+H6jsscKuB6VJafU+V4/hPURSbuFZIcmnD9MLmzAIhlQiCf0Fy6s/1MPPA==", + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/npm-run-script": "^3.0.0", - "@lerna/output": "^3.0.0", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "p-map": "^1.2.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@lerna/run-lifecycle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.2.0.tgz", - "integrity": "sha512-kGGdHJRyeZF+VTtal1DBptg6qwIsOLg3pKtmRm1rCMNN7j4kgrA9L07ZoRar8LjQXvfuheB1LSKHd5d04pr4Tg==", + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "requires": { - "@lerna/npm-conf": "^3.0.0", - "npm-lifecycle": "^2.0.0", - "npmlog": "^4.1.2" + "@babel/helper-plugin-utils": "^7.14.5" } }, - "@lerna/run-parallel-batches": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz", - "integrity": "sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw==", + "@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, "requires": { - "p-map": "^1.2.0", - "p-map-series": "^1.0.0" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@lerna/symlink-binary": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.1.4.tgz", - "integrity": "sha512-uQ8pYxzygahshXJAeC/vY4eSi+kFM0S2Pi15hJsJI3W7Ec6ysSYU1lXemb6kqoIqkTDJZWnfKXEq/3FLE+JYhg==", + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, "requires": { - "@lerna/create-symlink": "^3.0.0", - "@lerna/package": "^3.0.0", - "fs-extra": "^6.0.1", - "p-map": "^1.2.0", - "read-pkg": "^3.0.0" + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" } }, - "@lerna/symlink-dependencies": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.1.4.tgz", - "integrity": "sha512-iModwb0Xh0N0t55C6S4K2mzLdu1zXVsBc0qubUY1x0RSul92z8NeAe1aM5JzwMzuSoMA/LRiD1lNMWMRBf4JVg==", + "@babel/traverse": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", + "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", + "dev": true, "requires": { - "@lerna/create-symlink": "^3.0.0", - "@lerna/resolve-symlink": "^3.0.0", - "@lerna/symlink-binary": "^3.1.4", - "fs-extra": "^6.0.1", - "p-finally": "^1.0.0", - "p-map": "^1.2.0", - "p-map-series": "^1.0.0" + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.19.1", + "@babel/types": "^7.19.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } } }, - "@lerna/validation-error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.0.0.tgz", - "integrity": "sha512-5wjkd2PszV0kWvH+EOKZJWlHEqCTTKrWsvfHnHhcUaKBe/NagPZFWs+0xlsDPZ3DJt5FNfbAPAnEBQ05zLirFA==", + "@babel/types": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "dev": true, "requires": { - "npmlog": "^4.1.2" + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + } } }, - "@lerna/version": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.2.0.tgz", - "integrity": "sha512-1AVDMpeecSMiG1cacduE+f2KO0mC7F/9MvWsHtp+rjkpficMcsVme7IMtycuvu/F07wY4Xr9ioFKYTwTcybbIA==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/check-working-tree": "^3.1.0", - "@lerna/child-process": "^3.0.0", - "@lerna/collect-updates": "^3.1.0", - "@lerna/command": "^3.1.3", - "@lerna/conventional-commits": "^3.0.2", - "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.0.0", - "@lerna/run-lifecycle": "^3.2.0", - "@lerna/validation-error": "^3.0.0", - "chalk": "^2.3.1", - "dedent": "^0.7.0", - "minimatch": "^3.0.4", - "npmlog": "^4.1.2", - "p-map": "^1.2.0", - "p-pipe": "^1.2.0", - "p-reduce": "^1.0.0", - "p-waterfall": "^1.0.0", - "semver": "^5.5.0", - "slash": "^1.0.0", - "temp-write": "^3.4.0" - } + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true }, - "@lerna/write-log-file": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-3.0.0.tgz", - "integrity": "sha512-SfbPp29lMeEVOb/M16lJwn4nnx5y+TwCdd7Uom9umd7KcZP0NOvpnX0PHehdonl7TyHZ1Xx2maklYuCLbQrd/A==", - "requires": { - "npmlog": "^4.1.2", - "write-file-atomic": "^2.3.0" - } + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } } }, - "@nodelib/fs.stat": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz", - "integrity": "sha512-KU/VDjC5RwtDUZiz3d+DHXJF2lp5hB9dn552TXIyptj8SH1vXmR40mG0JgGq03IlYsOgGfcv8xrLpSQ0YUMQdA==" - }, - "JSONStream": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.4.tgz", - "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==", + "@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" } }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "requires": { - "es6-promisify": "^5.0.0" - } + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true }, - "agentkeepalive": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.1.tgz", - "integrity": "sha512-Cte/sTY9/XcygXjJ0q58v//SnEQ7ViWExKyJpLJlLqomDbQyMLh6Is4KuWJ/wmxzhiwkGRple7Gqv1zf6Syz5w==", - "requires": { - "humanize-ms": "^1.2.1" - } + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } } }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "is-buffer": "^1.1.5" + "has-flag": "^4.0.0" } } } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "@jest/environment": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", + "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", + "dev": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "@jest/fake-timers": "^29.0.1", + "@jest/types": "^29.0.1", + "@types/node": "*", + "jest-mock": "^29.0.1" + }, + "dependencies": { + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", + "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" - }, - "array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=" - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, "requires": { - "array-uniq": "^1.0.1" + "jest-get-type": "^28.0.2" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "@jest/fake-timers": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", + "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", + "dev": true, "requires": { - "safer-buffer": "~2.1.0" + "@jest/types": "^29.0.1", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.0.1", + "jest-mock": "^29.0.1", + "jest-util": "^29.0.1" + }, + "dependencies": { + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", + "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-message-util": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", + "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.0.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.0.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-util": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", + "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "pretty-format": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", + "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + }, + "dependencies": { + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + } + } }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "color-convert": "^2.0.1" } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { - "kind-of": "^6.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { - "kind-of": "^6.0.0" + "color-name": "~1.1.4" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "has-flag": "^4.0.0" } } } }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, "requires": { - "tweetnacl": "^0.14.3" + "@sinclair/typebox": "^0.24.1" } }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, "requires": { - "inherits": "~2.0.0" + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" } }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + "@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" }, "dependencies": { - "extend-shallow": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "is-extendable": "^0.1.0" + "has-flag": "^4.0.0" } } } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" - }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" - }, - "byte-size": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.3.tgz", - "integrity": "sha512-JGC3EV2bCzJH/ENSh3afyJrH4vwxbHTuO5ljLoI5+2iJOcEpMgP8T782jH9b5qGxf2mSUIp1lfGnfKNrRHpvVg==" - }, - "cacache": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.2.0.tgz", - "integrity": "sha512-IFWl6lfK6wSeYCHUXh+N1lY72UDrpyrYQJNIVQf48paDuWbv5RbAtJYf/4gUQFObTCHZwdZ5sI8Iw7nqwP6nlQ==", + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } } } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "dev": true, "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" - }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true }, - "ci-info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.4.0.tgz", - "integrity": "sha512-Oqmw2pVfCl8sCL+1QgMywPfdxPJPkC51y4usw0iiE2S9qnEOAqXy8bwl1CpMpnoU39g4iKJTz6QZj+28FvOnjQ==" - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" } }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "@react-native-async-storage/async-storage": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", + "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", + "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "merge-options": "^3.0.4" } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + "@react-native-community/netinfo": { + "version": "5.9.10", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", + "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", + "dev": true }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "@rollup/plugin-commonjs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", + "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", + "dev": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - } + "@rollup/pluginutils": "^3.0.8", + "commondir": "^1.0.1", + "estree-walker": "^1.0.1", + "glob": "^7.1.2", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0" } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" - }, - "cmd-shim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", - "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", + "@rollup/plugin-node-resolve": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", + "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" + "@rollup/pluginutils": "^3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.14.2" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "@sinclair/typebox": { + "version": "0.24.34", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", + "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, "requires": { - "color-name": "1.1.3" + "type-detect": "4.0.8" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "columnify": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", - "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" + "@sinonjs/commons": "^1.7.0" } }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, "requires": { - "delayed-stream": "~1.0.0" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, - "compare-func": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", - "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^3.0.0" - }, - "dependencies": { - "dot-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", - "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", - "requires": { - "is-obj": "^1.0.0" - } - } + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" } }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "config-chain": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", - "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "conventional-changelog-angular": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz", - "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", - "requires": { - "compare-func": "^1.3.1", - "q": "^1.5.1" - } - }, - "conventional-changelog-core": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz", - "integrity": "sha512-HvTE6RlqeEZ/NFPtQeFLsIDOLrGP3bXYr7lFLMhCVsbduF1MXIe8OODkwMFyo1i9ku9NWBwVnVn0jDmIFXjDRg==", - "requires": { - "conventional-changelog-writer": "^3.0.9", - "conventional-commits-parser": "^2.1.7", - "dateformat": "^3.0.0", - "get-pkg-repo": "^1.0.0", - "git-raw-commits": "^1.3.6", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^1.3.6", - "lodash": "^4.2.1", - "normalize-package-data": "^2.3.5", - "q": "^1.5.1", - "read-pkg": "^1.1.0", - "read-pkg-up": "^1.0.1", - "through2": "^2.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - } + "@babel/types": "^7.0.0" } }, - "conventional-changelog-preset-loader": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz", - "integrity": "sha512-MkksM4G4YdrMlT2MbTsV2F6LXu/hZR0Tc/yenRrDIKRwBl/SP7ER4ZDlglqJsCzLJi4UonBc52Bkm5hzrOVCcw==" - }, - "conventional-changelog-writer": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz", - "integrity": "sha512-n9KbsxlJxRQsUnK6wIBRnARacvNnN4C/nxnxCkH+B/R1JS2Fa+DiP1dU4I59mEDEjgnFaN2+9wr1P1s7GYB5/Q==", + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^1.1.6", - "dateformat": "^3.0.0", - "handlebars": "^4.0.2", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "semver": "^5.5.0", - "split": "^1.0.0", - "through2": "^2.0.0" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "conventional-commits-filter": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz", - "integrity": "sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q==", + "@types/babel__traverse": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", + "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "dev": true, "requires": { - "is-subset": "^0.1.1", - "modify-values": "^1.0.0" + "@babel/types": "^7.3.0" } }, - "conventional-commits-parser": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz", - "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==", - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.0", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.0.0", - "trim-off-newlines": "^1.0.0" - } + "@types/chai": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", + "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "dev": true + }, + "@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true }, - "conventional-recommended-bump": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-2.0.9.tgz", - "integrity": "sha512-YE6/o+648qkX3fTNvfBsvPW3tSnbZ6ec3gF0aBahCPgyoVHU2Mw0nUAZ1h1UN65GazpORngrgRC8QCltNYHPpQ==", + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "@types/eslint": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", + "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", + "dev": true, "requires": { - "concat-stream": "^1.6.0", - "conventional-changelog-preset-loader": "^1.1.8", - "conventional-commits-filter": "^1.1.6", - "conventional-commits-parser": "^2.1.7", - "git-raw-commits": "^1.3.6", - "git-semver-tags": "^1.3.6", - "meow": "^4.0.0", - "q": "^1.5.1" + "@types/estree": "*", + "@types/json-schema": "*" } }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" + "@types/eslint": "*", + "@types/estree": "*" } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true }, - "cosmiconfig": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", - "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0" + "@types/node": "*" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "@types/istanbul-lib-coverage": "*" } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, "requires": { - "array-find-index": "^1.0.1" + "@types/istanbul-lib-report": "*" } }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" + "@types/jest": { + "version": "23.3.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", + "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", + "dev": true }, - "dargs": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", - "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", + "@types/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "@types/nise": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", + "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", + "dev": true + }, + "@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, "requires": { - "ms": "2.0.0" + "@types/node": "*" } }, - "debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "@types/uuid": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", + "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", + "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", + "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/type-utils": "5.33.0", + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", + "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", + "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", + "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", + "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", + "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", + "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", + "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.33.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - } + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" } }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, "requires": { - "clone": "^1.0.2" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" }, "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true } } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, "requires": { - "asap": "^2.0.0", - "wrappy": "1" + "es6-promisify": "^5.0.0" } }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" } }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { - "is-obj": "^1.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true }, - "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { - "iconv-lite": "~0.4.13" + "color-convert": "^1.9.0" } }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, "requires": { - "once": "^1.4.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "err-code": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "sprintf-js": "~1.0.2" } }, - "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "dev": true }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, "requires": { - "es6-promise": "^4.0.3" + "safer-buffer": "~2.1.0" } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "dev": true, + "requires": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "color-convert": "^2.0.1" } - } - } - }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "extend-shallow": { + "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { - "is-extendable": "^0.1.0" + "color-name": "~1.1.4" } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "has-flag": "^4.0.0" } } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } }, - "fast-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", - "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", + "babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "dev": true, "requires": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.0.1", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.1", - "micromatch": "^3.1.10" - }, - "dependencies": { - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "requires": { - "is-extglob": "^2.1.1" - } - } + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" } }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" + } }, - "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, - "figures": { + "base64id": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "tweetnacl": "^0.14.3" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, "requires": { - "is-extendable": "^0.1.0" + "side-channel": "^1.0.4" } } } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { - "locate-path": "^2.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "fill-range": "^7.0.1" } }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" - } + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" } }, - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "https-proxy-agent": "^2.2.1" } }, - "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", - "requires": { - "minipass": "^2.2.1" + "browserstack-local": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", + "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + } } }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" + "fast-json-stable-stringify": "2.x" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" + "node-int64": "^0.4.0" } }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, - "genfun": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/genfun/-/genfun-4.0.1.tgz", - "integrity": "sha1-7RAEHy5KfxsKOEZtF6XD4n3x38E=" + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true }, - "get-pkg-repo": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", - "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", - "requires": { - "hosted-git-info": "^2.1.4", - "meow": "^3.3.0", - "normalize-package-data": "^2.3.0", - "parse-github-repo-url": "^1.3.0", - "through2": "^2.0.0" + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "requires": { - "repeating": "^2.0.0" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "requires": { - "get-stdin": "^4.0.1" - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" } } }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + "caniuse-lite": { + "version": "1.0.30001407", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", + "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", + "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, - "git-raw-commits": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz", - "integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==", + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, "requires": { - "dargs": "^4.0.1", - "lodash.template": "^4.0.2", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" } }, - "git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "git-semver-tags": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.3.6.tgz", - "integrity": "sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig==", - "requires": { - "meow": "^4.0.0", - "semver": "^5.5.0" - } + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true }, - "gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", - "requires": { - "ini": "^1.3.2" - } + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "color-name": "1.1.3" } }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, - "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "delayed-stream": "~1.0.0" } }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", - "requires": { - "async": "^1.4.0", - "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { - "amdefine": ">=0.0.4" + "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "object-assign": "^4", + "vary": "^1" } }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "coveralls": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { - "is-buffer": "^1.1.5" + "isexe": "^2.0.0" } } } }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" - }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, "requires": { - "agent-base": "4", - "debug": "3.1.0" + "cssom": "~0.3.6" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true } } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true }, - "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { - "agent-base": "^4.1.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } + "assert-plus": "^1.0.0" } }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, "requires": { - "ms": "^2.0.0" + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "date-format": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", + "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "ms": "2.1.2" } }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + "decimal.js": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "dev": true }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "requires": { - "minimatch": "^3.0.4" + "mimic-response": "^2.0.0" } }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" + "type-detect": "^4.0.0" } }, - "imurmurhash": { + "deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "init-package-json": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", - "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - } - }, - "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" + "path-type": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { + "path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true } } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { - "kind-of": "^3.0.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.256", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", + "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", + "dev": true + }, + "emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true } } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, "requires": { - "builtin-modules": "^1.0.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" } }, - "is-ci": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.0.tgz", - "integrity": "sha512-plgvKjQtalH2P3Gytb7L61Lmz95g2DlpzFiQyRSFew8WoJKxtKRzrZMeyRN2supblm3Psc8OQGy7Xjb6XG11jw==", + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { - "ci-info": "^1.3.0" + "is-arrayish": "^0.2.1" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, "requires": { - "kind-of": "^3.0.2" + "es6-promise": "^4.0.3" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, "requires": { - "is-buffer": "^1.1.5" + "prelude-ls": "~1.1.2" } } } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.3", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "get-stdin": "^6.0.0" } }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "prettier-linter-helpers": "^1.0.0" } }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, "requires": { - "is-extglob": "^2.1.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" } }, - "is-number": { + "eslint-utils": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "requires": { - "kind-of": "^3.0.2" + "eslint-visitor-keys": "^2.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true } } }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "espree": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", + "dev": true, "requires": { - "isobject": "^3.0.1" + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } }, - "is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "requires": { - "text-extensions": "^1.0.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "requires": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + }, + "dependencies": { + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "dev": true, + "requires": { + "samsam": "1.x" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "requires": { + "null-check": "^1.0.0" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + }, + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + } + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "dev": true, + "requires": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "requires": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", + "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", + "dev": true, + "requires": { + "@jest/environment": "^29.0.1", + "@jest/fake-timers": "^29.0.1", + "@jest/types": "^29.0.1", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.0.1", + "jest-util": "^29.0.1", + "jsdom": "^20.0.0" + }, + "dependencies": { + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", + "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-util": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", + "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "dev": true, + "requires": { + "@jest/types": "^29.0.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "dependencies": { + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + } + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-localstorage-mock": { + "version": "2.4.22", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", + "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", + "dev": true + }, + "jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-mock": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", + "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", + "dev": true, + "requires": { + "@jest/types": "^29.0.3", + "@types/node": "*" + }, + "dependencies": { + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/types": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", + "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true + }, + "jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true + }, + "jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "dev": true, + "requires": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" + } + }, + "jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -2817,1264 +6049,1420 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "^7.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } + } }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, "requires": { "graceful-fs": "^4.1.6" } }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "lerna": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.2.1.tgz", - "integrity": "sha512-nHa/TgRLOHlBm+NfeW62ffVO7hY7wJxnu6IJmZA3lrSmRlqrXZk2BPvnq0FSaCinVYjW0w0XeSNZdRKR//HAwQ==", - "requires": { - "@lerna/add": "^3.2.0", - "@lerna/bootstrap": "^3.2.0", - "@lerna/changed": "^3.2.0", - "@lerna/clean": "^3.1.3", - "@lerna/cli": "^3.2.0", - "@lerna/create": "^3.1.3", - "@lerna/diff": "^3.1.3", - "@lerna/exec": "^3.1.3", - "@lerna/import": "^3.1.3", - "@lerna/init": "^3.1.3", - "@lerna/link": "^3.1.4", - "@lerna/list": "^3.1.3", - "@lerna/publish": "^3.2.1", - "@lerna/run": "^3.1.3", - "@lerna/version": "^3.2.0", - "import-local": "^1.0.0", - "npmlog": "^4.1.2" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "lodash.templatesettings": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", - "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", - "requires": { - "lodash._reinterpolate": "~3.0.0" + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "karma": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", + "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } } }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, - "loud-rejection": { + "karma-browserstack-launcher": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", + "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", + "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "browserstack": "~1.5.1", + "browserstack-local": "^1.3.7", + "q": "~1.5.0" } }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "requires": { - "pify": "^3.0.0" - } - }, - "make-fetch-happen": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", - "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^11.0.1", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "lru-cache": "^4.1.2", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + "karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", + "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, "requires": { - "object-visit": "^1.0.0" + "fs-access": "^1.0.0", + "which": "^1.2.1" } }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "karma-mocha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", + "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=", + "dev": true, "requires": { - "mimic-fn": "^1.0.0" - } - }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" + "minimist": "1.2.0" }, "dependencies": { "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true } } }, - "merge2": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", - "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "karma-webpack": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", + "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "webpack-merge": "^4.1.5" } }, - "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true }, - "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "requires": { - "mime-db": "~1.36.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "p-locate": "^5.0.0" } }, - "minimist": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", - "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=" + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "log4js": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", + "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", + "dev": true, + "requires": { + "date-format": "^4.0.13", + "debug": "^4.3.4", + "flatted": "^3.2.6", + "rfdc": "^1.3.0", + "streamroller": "^3.1.2" + } + }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "get-func-name": "^2.0.0" } }, - "minipass": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", - "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" - } + "yallist": "^4.0.0" } }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } + "sourcemap-codec": "^1.4.8" } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "requires": { - "minimist": "0.0.8" + "semver": "^6.0.0" }, "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, - "modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==" - }, - "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" + "tmpl": "1.0.5" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" - } - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true }, - "node-fetch-npm": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dev": true, "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" + "is-plain-obj": "^2.1.0" } }, - "node-gyp": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", - "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", - "requires": { - "fstream": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^2.0.0", - "which": "1" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" - } - } + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1" - } + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, - "npm-bundled": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", - "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==" + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true }, - "npm-lifecycle": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-2.1.0.tgz", - "integrity": "sha512-QbBfLlGBKsktwBZLj6AviHC6Q9Y3R/AY4a2PYSIRhSKSS0/CxRyD/PfxEX6tPeOCXQgMSNdwGeECacstgptc+g==", - "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.11", - "node-gyp": "^3.8.0", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", - "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" - } + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true }, - "npm-package-arg": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", - "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "requires": { - "hosted-git-info": "^2.6.0", - "osenv": "^0.1.5", - "semver": "^5.5.0", - "validate-npm-package-name": "^3.0.0" + "mime-db": "1.52.0" } }, - "npm-packlist": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz", - "integrity": "sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, - "npm-pick-manifest": { + "mimic-response": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.1.0.tgz", - "integrity": "sha512-q9zLP8cTr8xKPmMZN3naxp1k/NxVFsjxN6uWuO1tiw9gxg7wZWQ/b5UTfzD0ANw2q1lQxdLKTeCCksq+bPSgbQ==", - "requires": { - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" - } + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" }, - "npm-registry-fetch": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.8.0.tgz", - "integrity": "sha512-hrw8UMD+Nob3Kl3h8Z/YjmKamb1gf7D1ZZch2otrIXM3uFLB5vjEY6DhMlq80z/zZet6eETLbOXcuQudCB3Zpw==", + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^4.1.3", - "make-fetch-happen": "^4.0.1", - "npm-package-arg": "^6.1.0" + "brace-expansion": "^1.1.7" } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "minimist": "^1.2.6" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "minimist": "0.0.8" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, "requires": { - "is-buffer": "^1.1.5" + "has-flag": "^3.0.0" } } } }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { + "mocha-lcov-reporter": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" - } + "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", + "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", + "dev": true }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "onetime": { + "murmurhash": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "requires": { - "mimic-fn": "^1.0.0" - } + "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", + "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - } - } + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "dev": true }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nise": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" }, "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "@sinonjs/commons": "^1.7.0" } } } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "nock": { + "version": "11.9.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", + "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", + "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.13", + "mkdirp": "^0.5.0", + "propagate": "^2.0.0" } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, "requires": { - "p-try": "^1.0.0" + "process-on-spawn": "^1.0.0" } }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, - "p-map-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", - "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=", + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "requires": { - "p-reduce": "^1.0.0" + "path-key": "^3.0.0" } }, - "p-pipe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz", - "integrity": "sha1-SxoROZoRUgpneQ7loMHViB1r7+k=" - }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "p-waterfall": { + "null-check": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-waterfall/-/p-waterfall-1.0.0.tgz", - "integrity": "sha1-ftlLPOszMngjU69qrhGqn8I1uwA=", - "requires": { - "p-reduce": "^1.0.0" - } + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true }, - "pacote": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.1.0.tgz", - "integrity": "sha512-AFXaSWhOtQf3jHqEvg+ZYH/dfT8TKq6TKspJ4qEFwVVuh5aGvMIk6SNF8vqfzz+cBceDIs9drOcpBbrPai7i+g==", - "requires": { - "bluebird": "^3.5.1", - "cacache": "^11.0.2", - "figgy-pudding": "^3.2.1", - "get-stream": "^3.0.0", - "glob": "^7.1.2", - "lru-cache": "^4.1.3", - "make-fetch-happen": "^4.0.1", - "minimatch": "^3.0.4", - "minipass": "^2.3.3", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.10", - "npm-pick-manifest": "^2.1.0", - "npm-registry-fetch": "^3.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.5.0", - "ssri": "^6.0.0", - "tar": "^4.4.3", - "unique-filename": "^1.1.0", - "which": "^1.3.0" + "nwsapi": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", + "dev": true + }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" }, "dependencies": { - "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, - "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, "requires": { - "minipass": "^2.2.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "tar": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", - "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.5", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" - } + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" } }, - "yallist": { + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, - "parse-github-repo-url": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", - "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=" + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "ee-first": "1.1.1" } }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { - "pify": "^3.0.0" + "wrappy": "1" } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "requires": { - "pinkie": "^2.0.0" + "mimic-fn": "^2.1.0" } }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, "requires": { - "find-up": "^2.1.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" - }, - "promise-retry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" + "yocto-queue": "^0.1.0" } }, - "promzard": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "requires": { - "read": "1" + "p-limit": "^3.0.2" } }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" - }, - "protoduck": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.0.tgz", - "integrity": "sha512-agsGWD8/RZrS4ga6v82Fxb0RHIS2RZnbsSue6A9/MBRhB/jcqOANAMNrqM9900b8duj+Gx+T/JMy5IowDoO/hQ==", + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, "requires": { - "genfun": "^4.0.1" + "aggregate-error": "^3.0.0" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "callsites": "^3.0.0" } }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" } }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "parse5": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "requires": { + "entities": "^4.3.0" + } }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, - "read": { + "path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "requires": { - "mute-stream": "~0.0.4" - } + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, - "read-cmd-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz", - "integrity": "sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=", + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2" + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } } }, - "read-package-json": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz", - "integrity": "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==", - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" - } + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true }, - "read-package-tree": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.1.tgz", - "integrity": "sha512-2CNoRoh95LxY47LvqrehIAfUVda2JbuFE/HaGYs42bNrGG+ojbw1h3zOcPcQ+1GQ3+rkzNndZn85u1XyZ3UsIA==", + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "once": "^1.3.0", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0" + "through": "~2.3" } }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "^4.0.0" }, "dependencies": { "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "p-locate": "^4.1.0" } }, - "pify": { + "p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "p-try": "^2.0.0" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "requires": { - "is-utf8": "^0.2.0" + "p-limit": "^2.2.0" } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true } } }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "fast-diff": "^1.1.2" } }, - "readdir-scoped-modules": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz", - "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=", + "pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" + "fromentries": "^1.2.0" } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "promise-polyfill": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", + "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" } }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, + "ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, "requires": { - "is-finite": "^1.0.0" + "es6-error": "^4.0.1" } }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -4083,7 +7471,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -4093,393 +7481,441 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } }, "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, "requires": { - "resolve-from": "^3.0.0" + "resolve-from": "^5.0.0" }, "dependencies": { "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true } } }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true }, - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "requires": { - "align-text": "^0.1.1" - } + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "^2.1.0" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "requires": { - "aproba": "^1.1.1" - } - }, - "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", - "requires": { - "symbol-observable": "1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { - "ret": "~0.1.10" + "glob": "^7.1.3" } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "rollup": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", + "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", + "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "fsevents": "~2.1.2" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true } } }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "rollup-plugin-terser": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", + "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", + "dev": true, "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" - }, - "smart-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", - "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "@babel/code-frame": "^7.5.5", + "jest-worker": "^24.9.0", + "rollup-pluginutils": "^2.8.2", + "serialize-javascript": "^4.0.0", + "terser": "^4.6.2" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, "requires": { - "is-extendable": "^0.1.0" + "has-flag": "^3.0.0" } } } }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "rollup-plugin-typescript2": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", + "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", + "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "@rollup/pluginutils": "^3.1.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "8.1.0", + "resolve": "1.17.0", + "tslib": "2.0.1" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, "requires": { - "kind-of": "^6.0.0" + "path-parse": "^1.0.6" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true } } }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, "requires": { - "kind-of": "^3.2.0" + "estree-walker": "^0.6.1" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true } } }, - "socks": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz", - "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==", + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "queue-microtask": "^1.2.2" } }, - "socks-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "xmlchars": "^2.2.0" } }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, "requires": { - "is-plain-obj": "^1.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "randombytes": "^2.1.0" } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true }, - "spdx-correct": { + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" } }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "sinon": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", + "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", + "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "diff": "^3.1.0", + "formatio": "1.2.0", + "lolex": "^1.6.0", + "native-promise-only": "^0.8.1", + "path-to-regexp": "^1.7.0", + "samsam": "^1.1.3", + "text-encoding": "0.6.4", + "type-detect": "^4.0.0" } }, - "spdx-license-ids": { + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "socket.io": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", + "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==", + "dev": true, "requires": { - "through": "2" + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.0.4" } }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "socket.io-parser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", + "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", + "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" } }, - "split2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", - "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, "requires": { - "through2": "^2.0.2" + "through": "2" } }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -4492,257 +7928,521 @@ "tweetnacl": "~0.14.0" } }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "escape-string-regexp": "^2.0.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true } } }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" + "duplexer": "~0.1.1" } }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + "streamroller": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.2.tgz", + "integrity": "sha512-wZswqzbgGGsXYIrBYhOE0yP+nQ6XRk7xDcYwuQAGTYXdyAUmvgVFE0YU1g5pvQT0m7GBaQfYcSnlHbapuK0H0A==", + "dev": true, + "requires": { + "date-format": "^4.0.13", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true }, - "strip-indent": { + "strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true }, - "strong-log-transformer": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-1.0.6.tgz", - "integrity": "sha1-9/uTdYpppXEUAYEnfuoMLrEwH6M=", - "requires": { - "byline": "^5.0.0", - "duplexer": "^0.1.1", - "minimist": "^0.1.0", - "moment": "^2.6.0", - "through": "^2.3.4" - } + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" - }, - "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, "dependencies": { - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" + "has-flag": "^4.0.0" } } } }, - "temp-dir": { + "supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" - }, - "temp-write": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz", - "integrity": "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=", - "requires": { - "graceful-fs": "^4.1.2", - "is-stream": "^1.1.0", - "make-dir": "^1.0.0", - "pify": "^3.0.0", - "temp-dir": "^1.0.0", - "uuid": "^3.0.1" - } + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true }, - "text-extensions": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.7.0.tgz", - "integrity": "sha512-AKXZeDq230UaSzaO5s3qQUZOaC7iKbzq0jOFL614R7d9R593HLqAOL0cYoqLdkNrjBSOdmoQI06yigq1TSBXAg==" + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "temp-fs": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", + "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", + "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "rimraf": "~2.5.2" + }, + "dependencies": { + "rimraf": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + } } }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" } }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, "requires": { - "kind-of": "^3.0.2" + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "requires": { - "is-buffer": "^1.1.5" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } } } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "terser-webpack-plugin": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", + "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", + "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "@jridgewell/trace-mapping": "^0.3.7", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.7.2" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + } } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "ts-jest": { + "version": "28.0.8", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", + "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^28.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "dependencies": { + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "ts-loader": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.1.tgz", + "integrity": "sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==", + "dev": true, "requires": { - "punycode": "^2.1.0" + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ts-mockito": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", + "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", + "dev": true, + "requires": { + "lodash": "^4.17.5" + } + }, + "ts-node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" }, "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } } } }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true }, - "trim-off-newlines": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", - "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=" + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -4750,206 +8450,292 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "typescript": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", - "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "prelude-ls": "^1.2.1" } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=" - }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true }, - "umask": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", - "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=" + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "media-typer": "0.3.0", + "mime-types": "~2.1.24" } }, - "unique-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", - "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "requires": { - "unique-slug": "^2.0.0" + "is-typedarray": "^1.0.0" } }, - "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "requires": { - "imurmurhash": "^0.1.4" - } + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "dev": true }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true }, - "unset-value": { + "unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } + "escalade": "^3.1.1", + "picocolors": "^1.0.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, "requires": { - "builtins": "^1.0.3" + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" } }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, "requires": { - "defaults": "^1.0.3" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" } }, "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true }, "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" } }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -4957,117 +8743,130 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "requires": { - "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "write-json-file": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", - "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=", - "requires": { - "detect-indent": "^5.0.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "pify": "^3.0.0", - "sort-keys": "^2.0.0", - "write-file-atomic": "^2.0.0" + "signal-exit": "^3.0.7" } }, - "write-pkg": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz", - "integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==", - "requires": { - "sort-keys": "^2.0.0", - "write-json-file": "^2.2.0" - } + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true }, - "xregexp": { + "xml-name-validator": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "requires": { - "camelcase": "^4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - } - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/packages/optimizely-sdk/package.json b/package.json similarity index 96% rename from packages/optimizely-sdk/package.json rename to package.json index f1e13e25c..fc3c3ba03 100644 --- a/packages/optimizely-sdk/package.json +++ b/package.json @@ -28,8 +28,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/optimizely-sdk" + "url": "git+https://github.com/optimizely/javascript-sdk.git" }, "license": "Apache-2.0", "engines": { @@ -41,7 +40,7 @@ "bugs": { "url": "https://github.com/optimizely/javascript-sdk/issues" }, - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/optimizely-sdk", + "homepage": "https://github.com/optimizely/javascript-sdk", "dependencies": { "decompress-response": "^4.2.1", "json-schema": "^0.4.0", diff --git a/packages/optimizely-sdk/.prettierrc b/packages/optimizely-sdk/.prettierrc deleted file mode 100644 index 62301e2e3..000000000 --- a/packages/optimizely-sdk/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false -} diff --git a/packages/optimizely-sdk/README.md b/packages/optimizely-sdk/README.md deleted file mode 100644 index 5e65ad0db..000000000 --- a/packages/optimizely-sdk/README.md +++ /dev/null @@ -1,252 +0,0 @@ -# Optimizely JavaScript SDK - -[![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) -[![npm](https://img.shields.io/npm/dm/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) -[![GitHub Actions](https://img.shields.io/github/actions/workflow/status/optimizely/javascript-sdk/javascript.yml)](https://github.com/optimizely/javascript-sdk/actions) -[![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) -[![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) - -This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). - -Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/welcome). - -Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feature-flagging/) for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap. - ---- - -## Get Started - -> For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. - -> For **React** applications, refer to the [React SDK developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-sdk). - -> For **React Native** applications, refer to the [JavaScript (React Native) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-native-sdk). - -> For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk). - -> For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: -> - [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) -> - [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) -> - [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) -> - [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) -> - [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) -> -> Note: These starter kits use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. - -### Prerequisites - -Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets any ES5-compliant JavaScript environment. We officially support: -- Node.js >= 8.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. -- [Modern Web Browsers, such as IE 10+, Firefox 21+, Safari 6+, and Chrome 23+](https://caniuse.com/#feat=es5) - -In addition, other environments are likely compatible but are not formally supported including: -- Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. -- [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. -- Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 - -### Requirements - -* JavaScript (Browser): Modern web browser that is ES5-compliant. - -* JavaScript (Node): Node 8.0+ - -* The following peer dependencies may be required for use in production: - -```json -{ - "json-schema@0.4.0": { - "licenses": [ - "AFLv2.1", - "BSD" - ], - "publisher": "Kris Zyp", - "repository": "https://github.com/kriszyp/json-schema" - }, - "murmurhash@2.0.1": { - "licenses": "MIT*", - "repository": "https://github.com/perezd/node-murmurhash" - }, - "uuid@8.3.2": { - "licenses": "MIT", - "repository": "https://github.com/kelektiv/node-uuid" - }, - "decompress-response@4.2.1": { - "licenses": "MIT", - "repository": "https://github.com/sindresorhus/decompress-response" - } -} -``` - -To regenerate this, run the following command: - -```sh -npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)' -``` - -and remove the self (`@optimizely/optimizely-sdk`) entry. - -> Note: The `jq` command line tool is required to run the above script. You may install `jq` to your environment by using Homebrew on MacOS using the command `brew install jq`. For Windows users, please visit the [JQ GitHub repository](https://github.com/stedolan/jq) to learn more. - -### Install the SDK - -Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk): - -Using `npm`: -```sh -npm install --save @optimizely/optimizely-sdk -``` - -Using `yarn`: -```sh -yarn add @optimizely/optimizely-sdk -``` - -Using `pnpm`: -```sh -pnpm add @optimizely/optimizely-sdk -``` - -Using `deno` (no installation required): -```javascript -import optimizely from "npm:@optimizely/optimizely-sdk" -``` -## Use the JavaScript SDK (Browser) - -See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. - -### Initialization (Browser) - -The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): - -```html -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.min.js"></script> - -<!-- You can also use the unminified version if necessary --> -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.js"></script> -``` - -When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. - -As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: - -```javascript -const optimizelyClient = window.optimizelySdk.createInstance({ - sdkKey: '<YOUR_SDK_KEY>', - // datafile: window.optimizelyDatafile, - // etc. -}) - -optimizelyClient.onReady().then(() => { - // Create the Optimizely user context, make decisions, and more here! -}) -``` - -Regarding `EventDispatcher`s: In Node.js and browser environments, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) modules and by [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Browser_compatibility), respectively. In all other environments, you must supply your own `EventDispatcher`. - -## Use the JavaScript SDK (Node) - -See the [Optimizely Feature Experimentation developer documentation for JavaScript (Node)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk) to learn how to set up your first JavaScript project and use the SDK for server-side applications. - -### Initialization (Node) - -The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): - -```typescript -import optimizelyClient from "@optimizely/optimizely-sdk"; - -optimizelyClient.createInstance({ - sdkKey: '<YOUR_SDK_KEY>', - // datafile: window.optimizelyDatafile, - // etc. -}); - -optimizelyClient.onReady().then(() => { - // Create the Optimizely user context, make decisions, and more here! -}) -``` - -Regarding `EventDispatcher`s: In Node.js environment, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) module. - -## SDK Development - -### Unit Tests - -There is a mix of testing paradigms used within the JavaScript SDK which include Mocha, Chai, Karma, and Jest, indicated by their respective `*.tests.js` and `*.spec.ts` filenames. - -When contributing code to the SDK, aim to keep the percentage of code test coverage at the current level ([![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk)) or above. - -To run unit tests on the primary JavaScript SDK package source code, you can take the following steps: - -1. On your command line or terminal, navigate to the `~/javascript-sdk/packages/optimizely-sdk` directory. -2. Ensure that you have run `npm install` to install all project dependencies. -3. Run `npm test` to run all test files. -4. (For cross-browser testing) Run `npm run test-xbrowser` to run tests in many browsers via BrowserStack. -5. Resolve any tests that fail before continuing with your contribution. - -This information is relevant only if you plan on contributing to the SDK itself. - -```sh -# Prerequisite: Install dependencies. -npm install - -# Run unit tests. -npm test - -# Run unit tests in many browsers, currently via BrowserStack. -# For this to work, the following environment variables must be set: -# - BROWSER_STACK_USERNAME -# - BROWSER_STACK_PASSWORD -npm run test-xbrowser -``` - -[/.github/workflows/javascript.yml](/.github/workflows/javascript.yml) contains the definitions for `BROWSER_STACK_USERNAME` and `BROWSER_STACK_ACCESS_KEY` used in the GitHub Actions CI pipeline. When developing locally, you must provide your own credentials in order to run `npm run test-xbrowser`. You can register for an account for free on [the BrowserStack official website here](https://www.browserstack.com/). - -### Contributing - -For more information regarding contributing to the Optimizely JavaScript SDK, please read [Contributing](CONTRIBUTING.md). - -## Special Notes - -### Migrating from 1.x.x - -This version represents a major version change and, as such, introduces some breaking changes: - -- The Node.js SDK is now combined with the JavaScript SDK. We now have just one package, `@optimizely/optimizely-sdk`, that works in many JavaScript environments. - -- We no longer support Node.js < 4.0.0, which collectively [reached end-of-life](https://github.com/nodejs/Release#end-of-life-releases) on 2016-12-31. - -- You will no longer be able to pass in `revenue` value as a stand-alone argument to the `track` call. Instead you will need to pass it as an entry in the [`eventTags`](https://developers.optimizely.com/x/solutions/sdks/reference/index.html?language=javascript#event-tags). - -### Feature Management access - -To access the Feature Management configuration in the Optimizely dashboard, please contact your Optimizely customer success manager. - -## Credits - -`@optimizely/optimizely-sdk` is developed and maintained by [Optimizely](https://optimizely.com) and many [contributors](https://github.com/optimizely/javascript-sdk/graphs/contributors). If you're interested in learning more about what Optimizely Feature Experimentation can do for your company you can visit the [official Optimizely Feature Experimentation product page here](https://www.optimizely.com/products/experiment/feature-experimentation/) to learn more. - -First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manager/lib`, `packages/datafile-manager/src`, `packages/datafile-manager/__test__`, `packages/event-processor/src`, `packages/event-processor/__tests__`, `packages/logging/src`, `packages/logging/__tests__`, `packages/utils/src`, `packages/utils/__tests__`) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. - -### Other Optimizely SDKs - -- Agent - https://github.com/optimizely/agent - -- Android - https://github.com/optimizely/android-sdk - -- C# - https://github.com/optimizely/csharp-sdk - -- Flutter - https://github.com/optimizely/optimizely-flutter-sdk - -- Go - https://github.com/optimizely/go-sdk - -- Java - https://github.com/optimizely/java-sdk - -- PHP - https://github.com/optimizely/php-sdk - -- Python - https://github.com/optimizely/python-sdk - -- React - https://github.com/optimizely/react-sdk - -- Ruby - https://github.com/optimizely/ruby-sdk - -- Swift - https://github.com/optimizely/swift-sdk \ No newline at end of file diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json deleted file mode 100644 index eb0b7d74f..000000000 --- a/packages/optimizely-sdk/package-lock.json +++ /dev/null @@ -1,8872 +0,0 @@ -{ - "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", - "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", - "dev": true - }, - "@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "dependencies": { - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "dev": true, - "requires": { - "@babel/types": "^7.19.0", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", - "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "dev": true, - "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "dev": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - } - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", - "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/node": "*", - "jest-mock": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "requires": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - } - }, - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "@jest/fake-timers": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", - "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.0.1", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } - } - }, - "@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", - "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", - "dev": true, - "requires": { - "merge-options": "^3.0.4" - } - }, - "@react-native-community/netinfo": { - "version": "5.9.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", - "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", - "dev": true - }, - "@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - } - }, - "@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@sinclair/typebox": { - "version": "0.24.34", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", - "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", - "dev": true - }, - "@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "@types/eslint": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", - "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "@types/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", - "dev": true - }, - "@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", - "dev": true - }, - "@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", - "dev": true - }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", - "dev": true - }, - "@types/uuid": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", - "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", - "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", - "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/type-utils": "5.33.0", - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", - "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", - "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", - "dev": true, - "requires": { - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", - "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", - "dev": true, - "requires": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - } - }, - "browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - } - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001407", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", - "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - } - }, - "date-format": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", - "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", - "dev": true - }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "requires": { - "webidl-conversions": "^7.0.0" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.256", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", - "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", - "dev": true - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "engine.io": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", - "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", - "dev": true, - "requires": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - }, - "dependencies": { - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true - } - } - }, - "engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", - "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.3", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - }, - "dependencies": { - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true - } - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "dependencies": { - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true - }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", - "dev": true, - "requires": { - "samsam": "1.x" - } - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true - }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - } - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "requires": { - "whatwg-encoding": "^2.0.0" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", - "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", - "dev": true, - "requires": { - "@jest/environment": "^29.0.1", - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1", - "jsdom": "^20.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-localstorage-mock": { - "version": "2.4.22", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", - "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", - "dev": true - }, - "jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", - "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", - "dev": true, - "requires": { - "@jest/types": "^29.0.3", - "@types/node": "*" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", - "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true - }, - "jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "requires": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - } - }, - "jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.8.0", - "xml-name-validator": "^4.0.0" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "karma": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", - "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - } - } - }, - "karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", - "dev": true, - "requires": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" - } - }, - "karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", - "dev": true - }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=", - "dev": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "webpack-merge": "^4.1.5" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "log4js": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", - "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", - "dev": true, - "requires": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "flatted": "^3.2.6", - "rfdc": "^1.3.0", - "streamroller": "^3.1.2" - } - }, - "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", - "dev": true - }, - "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "merge-options": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "dev": true, - "requires": { - "is-plain-obj": "^2.1.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "mocha-lcov-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "murmurhash": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", - "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" - }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - } - } - }, - "nock": { - "version": "11.9.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", - "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.13", - "mkdirp": "^0.5.0", - "propagate": "^2.0.0" - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "requires": { - "process-on-spawn": "^1.0.0" - } - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true - }, - "nwsapi": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", - "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", - "dev": true - }, - "nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "requires": { - "default-require-extensions": "^3.0.0" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, - "requires": { - "strip-bom": "^4.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "requires": { - "append-transform": "^2.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", - "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", - "dev": true, - "requires": { - "entities": "^4.3.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "requires": { - "fromentries": "^1.2.0" - } - }, - "promise-polyfill": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", - "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true - }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "requires": { - "event-stream": "=3.3.4" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", - "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", - "dev": true, - "requires": { - "fsevents": "~2.1.2" - }, - "dependencies": { - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - } - } - }, - "rollup-plugin-terser": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", - "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "jest-worker": "^24.9.0", - "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^4.0.0", - "terser": "^4.6.2" - }, - "dependencies": { - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "rollup-plugin-typescript2": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", - "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "8.1.0", - "resolve": "1.17.0", - "tslib": "2.0.1" - }, - "dependencies": { - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", - "dev": true - } - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - } - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, - "saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", - "dev": true, - "requires": { - "diff": "^3.1.0", - "formatio": "1.2.0", - "lolex": "^1.6.0", - "native-promise-only": "^0.8.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "socket.io": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", - "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.2.0", - "socket.io-adapter": "~2.4.0", - "socket.io-parser": "~4.0.4" - } - }, - "socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", - "dev": true - }, - "socket.io-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", - "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", - "dev": true, - "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "requires": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, - "streamroller": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.2.tgz", - "integrity": "sha512-wZswqzbgGGsXYIrBYhOE0yP+nQ6XRk7xDcYwuQAGTYXdyAUmvgVFE0YU1g5pvQT0m7GBaQfYcSnlHbapuK0H0A==", - "dev": true, - "requires": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, - "temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", - "dev": true, - "requires": { - "rimraf": "~2.5.2" - }, - "dependencies": { - "rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", - "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.7", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.7.2" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "dependencies": { - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "ts-loader": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.1.tgz", - "integrity": "sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, - "ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", - "dev": true, - "requires": { - "xml-name-validator": "^4.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true - }, - "webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true - }, - "whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true - }, - "xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/packages/optimizely-sdk/rollup.config.js b/rollup.config.js similarity index 100% rename from packages/optimizely-sdk/rollup.config.js rename to rollup.config.js diff --git a/packages/optimizely-sdk/srcclr.yml b/srcclr.yml similarity index 100% rename from packages/optimizely-sdk/srcclr.yml rename to srcclr.yml diff --git a/packages/optimizely-sdk/tests/backoffController.spec.ts b/tests/backoffController.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/backoffController.spec.ts rename to tests/backoffController.spec.ts diff --git a/packages/optimizely-sdk/tests/browserAsyncStorageCache.spec.ts b/tests/browserAsyncStorageCache.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/browserAsyncStorageCache.spec.ts rename to tests/browserAsyncStorageCache.spec.ts diff --git a/packages/optimizely-sdk/tests/browserDatafileManager.spec.ts b/tests/browserDatafileManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/browserDatafileManager.spec.ts rename to tests/browserDatafileManager.spec.ts diff --git a/packages/optimizely-sdk/tests/browserRequest.spec.ts b/tests/browserRequest.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/browserRequest.spec.ts rename to tests/browserRequest.spec.ts diff --git a/packages/optimizely-sdk/tests/browserRequestHandler.spec.ts b/tests/browserRequestHandler.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/browserRequestHandler.spec.ts rename to tests/browserRequestHandler.spec.ts diff --git a/packages/optimizely-sdk/tests/buildEventV1.spec.ts b/tests/buildEventV1.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/buildEventV1.spec.ts rename to tests/buildEventV1.spec.ts diff --git a/packages/optimizely-sdk/tests/eventEmitter.spec.ts b/tests/eventEmitter.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/eventEmitter.spec.ts rename to tests/eventEmitter.spec.ts diff --git a/packages/optimizely-sdk/tests/eventQueue.spec.ts b/tests/eventQueue.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/eventQueue.spec.ts rename to tests/eventQueue.spec.ts diff --git a/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts b/tests/httpPollingDatafileManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts rename to tests/httpPollingDatafileManager.spec.ts diff --git a/packages/optimizely-sdk/tests/httpPollingDatafileManagerPolling.spec.ts b/tests/httpPollingDatafileManagerPolling.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/httpPollingDatafileManagerPolling.spec.ts rename to tests/httpPollingDatafileManagerPolling.spec.ts diff --git a/packages/optimizely-sdk/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/index.react_native.spec.ts rename to tests/index.react_native.spec.ts diff --git a/packages/optimizely-sdk/tests/jsconfig.json b/tests/jsconfig.json similarity index 100% rename from packages/optimizely-sdk/tests/jsconfig.json rename to tests/jsconfig.json diff --git a/packages/optimizely-sdk/tests/logger.spec.ts b/tests/logger.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/logger.spec.ts rename to tests/logger.spec.ts diff --git a/packages/optimizely-sdk/tests/nodeDatafileManager.spec.ts b/tests/nodeDatafileManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/nodeDatafileManager.spec.ts rename to tests/nodeDatafileManager.spec.ts diff --git a/packages/optimizely-sdk/tests/nodeRequest.spec.ts b/tests/nodeRequest.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/nodeRequest.spec.ts rename to tests/nodeRequest.spec.ts diff --git a/packages/optimizely-sdk/tests/nodeRequestHandler.spec.ts b/tests/nodeRequestHandler.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/nodeRequestHandler.spec.ts rename to tests/nodeRequestHandler.spec.ts diff --git a/packages/optimizely-sdk/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/odpEventApiManager.spec.ts rename to tests/odpEventApiManager.spec.ts diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/odpEventManager.spec.ts rename to tests/odpEventManager.spec.ts diff --git a/packages/optimizely-sdk/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/odpManager.browser.spec.ts rename to tests/odpManager.browser.spec.ts diff --git a/packages/optimizely-sdk/tests/odpManager.spec.ts b/tests/odpManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/odpManager.spec.ts rename to tests/odpManager.spec.ts diff --git a/packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts b/tests/odpSegmentApiManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/odpSegmentApiManager.spec.ts rename to tests/odpSegmentApiManager.spec.ts diff --git a/packages/optimizely-sdk/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/odpSegmentManager.spec.ts rename to tests/odpSegmentManager.spec.ts diff --git a/packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts b/tests/pendingEventsDispatcher.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts rename to tests/pendingEventsDispatcher.spec.ts diff --git a/packages/optimizely-sdk/tests/pendingEventsStore.spec.ts b/tests/pendingEventsStore.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/pendingEventsStore.spec.ts rename to tests/pendingEventsStore.spec.ts diff --git a/packages/optimizely-sdk/tests/reactNativeAsyncStorageCache.spec.ts b/tests/reactNativeAsyncStorageCache.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/reactNativeAsyncStorageCache.spec.ts rename to tests/reactNativeAsyncStorageCache.spec.ts diff --git a/packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts b/tests/reactNativeEventsStore.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts rename to tests/reactNativeEventsStore.spec.ts diff --git a/packages/optimizely-sdk/tests/requestTracker.spec.ts b/tests/requestTracker.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/requestTracker.spec.ts rename to tests/requestTracker.spec.ts diff --git a/packages/optimizely-sdk/tests/testUtils.ts b/tests/testUtils.ts similarity index 100% rename from packages/optimizely-sdk/tests/testUtils.ts rename to tests/testUtils.ts diff --git a/packages/optimizely-sdk/tests/utils.spec.ts b/tests/utils.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/utils.spec.ts rename to tests/utils.spec.ts diff --git a/packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts b/tests/v1EventProcessor.react_native.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts rename to tests/v1EventProcessor.react_native.spec.ts diff --git a/packages/optimizely-sdk/tests/v1EventProcessor.spec.ts b/tests/v1EventProcessor.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/v1EventProcessor.spec.ts rename to tests/v1EventProcessor.spec.ts diff --git a/packages/optimizely-sdk/tests/vuidManager.spec.ts b/tests/vuidManager.spec.ts similarity index 100% rename from packages/optimizely-sdk/tests/vuidManager.spec.ts rename to tests/vuidManager.spec.ts diff --git a/packages/optimizely-sdk/tsconfig.json b/tsconfig.json similarity index 100% rename from packages/optimizely-sdk/tsconfig.json rename to tsconfig.json diff --git a/packages/optimizely-sdk/tsconfig.spec.json b/tsconfig.spec.json similarity index 100% rename from packages/optimizely-sdk/tsconfig.spec.json rename to tsconfig.spec.json diff --git a/packages/optimizely-sdk/typings/murmurhash.d.ts b/typings/murmurhash.d.ts similarity index 100% rename from packages/optimizely-sdk/typings/murmurhash.d.ts rename to typings/murmurhash.d.ts From 36ff5d7539e87a25ae24ec4aca8b538c925dfc92 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 18 Aug 2023 01:01:45 +0600 Subject: [PATCH 036/200] [FSSDK-9562] set pixel api endpoint dynamically (#852) --- lib/index.browser.tests.js | 6 ++++-- .../odp/event_api_manager/index.browser.ts | 20 +++++++++++++++++-- lib/plugins/odp_manager/index.node.ts | 2 +- lib/utils/enums/index.ts | 1 - 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index b813c9c34..9dc5b1d67 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -26,7 +26,7 @@ import configValidator from './utils/config_validator'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import OptimizelyUserContext from './optimizely_user_context'; -import { LOG_MESSAGES, ODP_EVENT_ACTION, ODP_EVENT_BROWSER_ENDPOINT } from './utils/enums'; +import { LOG_MESSAGES, ODP_EVENT_ACTION } from './utils/enums'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import { OdpConfig } from './core/odp/odp_config'; import { BrowserOdpEventManager } from './plugins/odp/event_manager/index.browser'; @@ -1031,6 +1031,7 @@ describe('javascript-sdk (Browser)', function() { it('should send an odp event to the browser endpoint', async () => { const odpConfig = new OdpConfig(); + const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); const eventManager = new BrowserOdpEventManager({ odpConfig, @@ -1065,8 +1066,9 @@ describe('javascript-sdk (Browser)', function() { let publicKey = datafile.integrations[0].publicKey; + const pixelApiEndpoint = 'https://jumbe.zaius.com/v2/zaius.gif'; let requestEndpoint = new URL(requestParams.get('endpoint')); - assert.equal(requestEndpoint.origin + requestEndpoint.pathname, ODP_EVENT_BROWSER_ENDPOINT); + assert.equal(requestEndpoint.origin + requestEndpoint.pathname, pixelApiEndpoint); assert.equal(requestParams.get('method'), 'GET'); let searchParams = requestEndpoint.searchParams; diff --git a/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/plugins/odp/event_api_manager/index.browser.ts index 5a13b8c06..7c2baef70 100644 --- a/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/lib/plugins/odp/event_api_manager/index.browser.ts @@ -1,11 +1,12 @@ import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; -import { ODP_EVENT_BROWSER_ENDPOINT } from '../../../utils/enums'; import { ODP_CONFIG_NOT_READY_MESSAGE } from '../../../core/odp/odp_event_api_manager'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; +const pixelApiPath = 'v2/zaius.gif'; + export class BrowserOdpEventApiManager extends OdpEventApiManager { protected shouldSendEvents(events: OdpEvent[]): boolean { if (events.length <= 1) { @@ -15,6 +16,15 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { return false; } + private getPixelApiEndpoint(): string { + if (!this.odpConfig?.isReady()) { + throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); + } + const apiHost = this.odpConfig.apiHost; + const pixelApiEndpoint = new URL(pixelApiPath, apiHost.replace('api', 'jumbe')).href; + return pixelApiEndpoint; + } + protected generateRequestData( events: OdpEvent[] ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { @@ -23,11 +33,17 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); } + + // this cannot be cached cause OdpConfig is mutable + // and can be updated in place and it is done so in odp + // manager. We should make OdpConfig immutable and + // refacator later + const pixelApiEndpoint = this.getPixelApiEndpoint(); const apiKey = this.odpConfig.apiKey; const method = 'GET'; const event = events[0]; - const url = new URL(ODP_EVENT_BROWSER_ENDPOINT); + const url = new URL(pixelApiEndpoint); event.identifiers.forEach((v, k) => { url.searchParams.append(k, v); }); diff --git a/lib/plugins/odp_manager/index.node.ts b/lib/plugins/odp_manager/index.node.ts index bee3bf19e..38d9bbdf2 100644 --- a/lib/plugins/odp_manager/index.node.ts +++ b/lib/plugins/odp_manager/index.node.ts @@ -114,7 +114,7 @@ export class NodeOdpManager extends OdpManager { }); } - this.eventManager!.start(); + this.eventManager.start(); this.initPromise = Promise.resolve(); } diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 41c027740..5b454abe2 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -357,7 +357,6 @@ export enum ODP_USER_KEY { export const FS_USER_ID_ALIAS = 'fs-user-id'; export const ODP_DEFAULT_EVENT_TYPE = 'fullstack'; -export const ODP_EVENT_BROWSER_ENDPOINT = 'https://jumbe.zaius.com/v2/zaius.gif'; /** * ODP Event Action Options From 2125cdeb5a21281c5dbf619cecff8ca1488e744e Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 18 Aug 2023 20:28:37 +0600 Subject: [PATCH 037/200] [FSSDK-9562] prepare for release 5.0.0-beta3 (#853) --- CHANGELOG.md | 8 ++++++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 4 ++-- package-lock.json | 2 +- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42706859a..3ab59f855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.0.0-beta3] - August 18, 2023 + +### Bug fixes +- Fixed odp event sending not working for Europe and Asia-Pacific regions ([#852](https://github.com/optimizely/javascript-sdk/pull/852)) + +### Changed +- Remove 1 second polling floor to allow datafile polling at any frequency but for intervals under 30 seconds, log a warning ([#841](https://github.com/optimizely/javascript-sdk/pull/841)). + ## [5.0.0-beta2] - July 19, 2023 ### Performance Improvements diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 9dc5b1d67..0fec0d5bc 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -188,7 +188,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta2'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta3'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index fb0b1fc1e..248932dc1 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta2'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta3'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 7327032a4..b68dcfd83 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta2'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta3'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 5b454abe2..d760722e9 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -223,8 +223,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '5.0.0-beta2'; -export const NODE_CLIENT_VERSION = '5.0.0-beta2'; +export const BROWSER_CLIENT_VERSION = '5.0.0-beta3'; +export const NODE_CLIENT_VERSION = '5.0.0-beta3'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index eb0b7d74f..253922ace 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta2", + "version": "5.0.0-beta3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fc3c3ba03..7aedd6626 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta2", + "version": "5.0.0-beta3", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index afa83d52d..d1fda26fc 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.0.0-beta2'); + expect(optlyInstance.clientVersion).toEqual('5.0.0-beta3'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From b85af27d762a4e1924c919cf0278e52d96e79ecf Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 21 Aug 2023 22:01:19 +0600 Subject: [PATCH 038/200] [FSSDK-8651] added support for user agent parser for odp (#854) --- .vscode/settings.json | 3 +- lib/core/odp/odp_event_manager.ts | 35 ++++++++++++- lib/core/odp/user_agent_info.ts | 26 ++++++++++ lib/core/odp/user_agent_parser.ts | 21 ++++++++ lib/index.browser.tests.js | 51 +++++++++++++++++++ lib/index.browser.ts | 6 +++ .../odp/user_agent_parser/index.browser.ts | 33 ++++++++++++ lib/plugins/odp_manager/index.browser.ts | 1 + lib/shared_types.ts | 2 + package-lock.json | 19 +++++-- package.json | 2 + tests/odpEventManager.spec.ts | 37 ++++++++++++++ tests/odpManager.browser.spec.ts | 7 ++- 13 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 lib/core/odp/user_agent_info.ts create mode 100644 lib/core/odp/user_agent_parser.ts create mode 100644 lib/plugins/odp/user_agent_parser/index.browser.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 67bbd4ff1..7869db3b0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "jest.rootPath": "/workspaces/javascript-sdk/packages/optimizely-sdk", "jest.jestCommandLine": "./node_modules/.bin/jest", - "jest.autoRevealOutput": "on-exec-error" + "jest.autoRevealOutput": "on-exec-error", + "editor.tabSize": 2 } \ No newline at end of file diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index e8a6744e2..934c2d2fb 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -23,6 +23,7 @@ import { OdpEvent } from './odp_event'; import { OdpConfig } from './odp_config'; import { IOdpEventApiManager } from './odp_event_api_manager'; import { invalidOdpDataFound } from './odp_utils'; +import { IUserAgentParser } from './user_agent_parser'; const MAX_RETRIES = 3; @@ -123,6 +124,19 @@ export abstract class OdpEventManager implements IOdpEventManager { */ private readonly clientVersion: string; + /** + * Version of the client being used + * @private + */ + private readonly userAgentParser?: IUserAgentParser; + + + /** + * Information about the user agent + * @private + */ + private readonly userAgentData?: Map<string, unknown>; + constructor({ odpConfig, apiManager, @@ -132,6 +146,7 @@ export abstract class OdpEventManager implements IOdpEventManager { queueSize, batchSize, flushInterval, + userAgentParser, }: { odpConfig: OdpConfig; apiManager: IOdpEventApiManager; @@ -141,6 +156,7 @@ export abstract class OdpEventManager implements IOdpEventManager { queueSize?: number; batchSize?: number; flushInterval?: number; + userAgentParser?: IUserAgentParser; }) { this.odpConfig = odpConfig; this.apiManager = apiManager; @@ -149,6 +165,22 @@ export abstract class OdpEventManager implements IOdpEventManager { this.clientVersion = clientVersion; this.initParams(batchSize, queueSize, flushInterval); this.state = STATE.STOPPED; + this.userAgentParser = userAgentParser; + + if (userAgentParser) { + const { os, device } = userAgentParser.parseUserAgentInfo(); + + const userAgentInfo: Record<string, unknown> = { + 'os': os.name, + 'os_version': os.version, + 'device_type': device.type, + 'model': device.model, + }; + + this.userAgentData = new Map<string, unknown>( + Object.entries(userAgentInfo).filter(([key, value]) => value != null && value != undefined) + ); + } this.apiManager.updateSettings(odpConfig); } @@ -408,7 +440,8 @@ export abstract class OdpEventManager implements IOdpEventManager { * @private */ private augmentCommonData(sourceData: Map<string, unknown>): Map<string, unknown> { - const data = new Map<string, unknown>(); + const data = new Map<string, unknown>(this.userAgentData); + data.set('idempotence_id', uuid()); data.set('data_source_type', 'sdk'); data.set('data_source', this.clientEngine); diff --git a/lib/core/odp/user_agent_info.ts b/lib/core/odp/user_agent_info.ts new file mode 100644 index 000000000..e83b3b032 --- /dev/null +++ b/lib/core/odp/user_agent_info.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type UserAgentInfo = { + os: { + name?: string, + version?: string, + }, + device: { + type?: string, + model?: string, + } +}; diff --git a/lib/core/odp/user_agent_parser.ts b/lib/core/odp/user_agent_parser.ts new file mode 100644 index 000000000..227065fb7 --- /dev/null +++ b/lib/core/odp/user_agent_parser.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { UserAgentInfo } from "./user_agent_info"; + +export interface IUserAgentParser { + parseUserAgentInfo(): UserAgentInfo, +} diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 0fec0d5bc..691723cc1 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -67,6 +67,10 @@ if (!global.window) { } } +const pause = (timeoutMilliseconds) => { + return new Promise(resolve => setTimeout(resolve, timeoutMilliseconds)); +}; + describe('javascript-sdk (Browser)', function() { var clock; beforeEach(function() { @@ -838,6 +842,53 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.called(fakeEventManager.sendEvent); }); + it('should augment odp events with user agent data if userAgentParser is provided', async () => { + const userAgentParser = { + parseUserAgentInfo() { + return { + os: { 'name': 'windows', 'version': '11' }, + device: { 'type': 'laptop', 'model': 'thinkpad' }, + } + } + } + + const fakeRequestHandler = { + makeRequest: sinon.spy(function (requestUrl, headers, method, data) { + return { + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200 }), + } + }) + }; + + const client = optimizelyFactory.createInstance({ + datafile: testData.getOdpIntegratedConfigWithSegments(), + errorHandler: fakeErrorHandler, + eventDispatcher: fakeEventDispatcher, + eventBatchSize: null, + logger, + odpOptions: { + userAgentParser, + eventRequestHandler: fakeRequestHandler, + }, + }); + const readyData = await client.onReady(); + + assert.equal(readyData.success, true); + assert.isUndefined(readyData.reason); + + client.sendOdpEvent('test', '', new Map([['eamil', 'test@test.test']]), new Map([['key', 'value']])); + clock.tick(10000); + + const eventRequestUrl = new URL(fakeRequestHandler.makeRequest.lastCall.args[0]); + const searchParams = eventRequestUrl.searchParams; + + assert.equal(searchParams.get('os'), 'windows'); + assert.equal(searchParams.get('os_version'), '11'); + assert.equal(searchParams.get('device_type'), 'laptop'); + assert.equal(searchParams.get('model'), 'thinkpad'); + }); + it('should convert fs-user-id, FS-USER-ID, and FS_USER_ID to fs_user_id identifier when calling sendOdpEvent', async () => { const fakeEventManager = { updateSettings: sinon.spy(), diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 39f7ccde5..56df474a8 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -28,6 +28,8 @@ import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './sha import { createHttpPollingDatafileManager } from './plugins/datafile_manager/browser_http_polling_datafile_manager'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import Optimizely from './optimizely'; +import { IUserAgentParser } from './core/odp/user_agent_parser'; +import { getUserAgentParser } from './plugins/odp/user_agent_parser/index.browser'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -164,6 +166,7 @@ const __internalResetRetryState = function(): void { const setLogHandler = logHelper.setLogHandler; const setLogLevel = logHelper.setLogLevel; + export { loggerPlugin as logging, defaultErrorHandler as errorHandler, @@ -174,6 +177,8 @@ export { createInstance, __internalResetRetryState, OptimizelyDecideOption, + IUserAgentParser, + getUserAgentParser, }; export default { @@ -186,6 +191,7 @@ export default { createInstance, __internalResetRetryState, OptimizelyDecideOption, + getUserAgentParser, }; export * from './export_types'; diff --git a/lib/plugins/odp/user_agent_parser/index.browser.ts b/lib/plugins/odp/user_agent_parser/index.browser.ts new file mode 100644 index 000000000..ad5d5f230 --- /dev/null +++ b/lib/plugins/odp/user_agent_parser/index.browser.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { UAParser } from 'ua-parser-js'; +import { UserAgentInfo } from "../../../core/odp/user_agent_info"; +import { IUserAgentParser } from '../../../core/odp/user_agent_parser'; + +const userAgentParser: IUserAgentParser = { + parseUserAgentInfo(): UserAgentInfo { + const parser = new UAParser(); + const agentInfo = parser.getResult(); + const { os, device } = agentInfo; + return { os, device }; + } +} + +export function getUserAgentParser(): IUserAgentParser { + return userAgentParser; +} + diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index d7c29c493..989774e5a 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -118,6 +118,7 @@ export class BrowserOdpManager extends OdpManager { flushInterval: odpOptions?.eventFlushInterval, batchSize: odpOptions?.eventBatchSize, queueSize: odpOptions?.eventQueueSize, + userAgentParser: odpOptions?.userAgentParser, }); } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index ff6df7bd7..4ef0de403 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -35,6 +35,7 @@ import { IOdpSegmentManager } from './core/odp/odp_segment_manager'; import { IOdpEventApiManager } from './core/odp/odp_event_api_manager'; import { IOdpEventManager } from './core/odp/odp_event_manager'; import { IOdpManager } from './core/odp/odp_manager'; +import { IUserAgentParser } from './core/odp/user_agent_parser'; export interface BucketerParams { experimentId: string; @@ -105,6 +106,7 @@ export interface OdpOptions { eventApiTimeout?: number; eventRequestHandler?: RequestHandler; eventManager?: IOdpEventManager; + userAgentParser?: IUserAgentParser; } export interface ListenerPayload { diff --git a/package-lock.json b/package-lock.json index 253922ace..3b4c236f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1656,6 +1656,12 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, + "@types/ua-parser-js": { + "version": "0.7.36", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", + "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", + "dev": true + }, "@types/uuid": { "version": "3.4.10", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", @@ -6260,6 +6266,12 @@ "requires": { "rimraf": "^3.0.0" } + }, + "ua-parser-js": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", + "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "dev": true } } }, @@ -8500,10 +8512,9 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", + "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==" }, "universalify": { "version": "0.1.2", diff --git a/package.json b/package.json index 7aedd6626..828de12c0 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "decompress-response": "^4.2.1", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", + "ua-parser-js": "^1.0.35", "uuid": "^8.3.2" }, "devDependencies": { @@ -57,6 +58,7 @@ "@types/mocha": "^5.2.7", "@types/nise": "^1.4.0", "@types/node": "^18.7.18", + "@types/ua-parser-js": "^0.7.36", "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 466b0368b..56c98da46 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -23,6 +23,8 @@ import { anything, capture, instance, mock, resetCalls, spy, verify, when } from import { IOdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; import { LogHandler, LogLevel } from '../lib/modules/logging'; import { OdpEvent } from '../lib/core/odp/odp_event'; +import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; +import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; @@ -372,6 +374,41 @@ describe('OdpEventManager', () => { expect(events[1].data.size).toEqual(PROCESSED_EVENTS[1].data.size); }); + it('should augment events with data from user agent parser', async () => { + const userAgentParser : IUserAgentParser = { + parseUserAgentInfo: function (): UserAgentInfo { + return { + os: { 'name': 'windows', 'version': '11' }, + device: { 'type': 'laptop', 'model': 'thinkpad' }, + } + } + } + + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 10, + flushInterval: 100, + userAgentParser, + }); + + eventManager.start(); + EVENTS.forEach(event => eventManager.sendEvent(event)); + await pause(1000); + + verify(mockApiManager.sendEvents(anything())).called(); + const [events] = capture(mockApiManager.sendEvents).last(); + const event = events[0]; + + expect(event.data.get('os')).toEqual('windows'); + expect(event.data.get('os_version')).toEqual('11'); + expect(event.data.get('device_type')).toEqual('laptop'); + expect(event.data.get('model')).toEqual('thinkpad'); + }); + it('should retry failed events', async () => { // all events should fail ie shouldRetry = true when(mockApiManager.sendEvents(anything())).thenResolve(true); diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index 7f6a265d8..385616593 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { anything, capture, instance, mock, resetCalls, verify } from 'ts-mockito'; +import { anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LOG_MESSAGES, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from './../lib/utils/enums/index'; import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; @@ -24,7 +24,7 @@ import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; import { BrowserOdpManager } from './../lib/plugins/odp_manager/index.browser'; -import { OdpOptions } from './../lib/shared_types'; +import { IOdpEventManager, OdpOptions } from './../lib/shared_types'; import { OdpConfig } from '../lib/core/odp/odp_config'; import { BrowserOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.browser'; import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.browser'; @@ -32,6 +32,9 @@ import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; +import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; +import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; +import { OdpEvent } from '../lib/core/odp/odp_event'; const keyA = 'key-a'; const hostA = 'host-a'; From b3170689f236e1dc07b3139ef7de6807640f1c4f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 22 Aug 2023 23:47:24 +0600 Subject: [PATCH 039/200] [FSSDK-9611] fix relative import paths that goes out of lib (#856) we are bundling our package, and type definiton of all our dependencies are in dev-dependencies as they should be and therefore not installed when clients install our sdk.compiling our lib files will need those type definitions. so files in our dist must not refer to the lib files, but some of them do refer to the lib files. and consumers are getting missing types as a result. files in /dist was referring to files in /lib because some relative import paths were going out of lib and then back in. as the path strings were preserved in the built files in dist, it was going out of /dist and into /lib. This commit fixes those relative import paths to stay inside /lib --- lib/core/odp/odp_config.ts | 2 +- .../httpPollingDatafileManager.ts | 4 ++-- lib/optimizely/index.ts | 2 -- lib/optimizely_user_context/index.ts | 4 ++-- .../forwarding_event_processor.ts | 4 ++-- .../event_processor/index.react_native.ts | 4 ++-- lib/plugins/event_processor/index.ts | 4 ++-- .../odp/event_manager/index.browser.ts | 18 ++++++++++++++- lib/plugins/odp/event_manager/index.node.ts | 22 ++++++++++++++++--- lib/plugins/odp_manager/index.node.ts | 2 +- lib/shared_types.ts | 6 ++--- lib/utils/event_tag_utils/index.ts | 4 ++-- 12 files changed, 53 insertions(+), 23 deletions(-) diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index 4fc7ae298..dcfb008e9 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { checkArrayEquality } from '../../../lib/utils/fns'; +import { checkArrayEquality } from '../../utils/fns'; export class OdpConfig { /** diff --git a/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/lib/modules/datafile-manager/httpPollingDatafileManager.ts index ee595a526..d71a4d3ef 100644 --- a/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ b/lib/modules/datafile-manager/httpPollingDatafileManager.ts @@ -15,7 +15,7 @@ */ import { getLogger } from '../logging'; -import { sprintf } from '../../../lib/utils/fns'; +import { sprintf } from '../../utils/fns'; import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; import EventEmitter, { Disposer } from './eventEmitter'; import { AbortableRequest, Response, Headers } from './http'; @@ -24,7 +24,7 @@ import BackoffController from './backoffController'; import PersistentKeyValueCache from './persistentKeyValueCache'; import { NotificationRegistry } from './../../core/notification_center/notification_registry'; -import { NOTIFICATION_TYPES } from '../../../lib/utils/enums'; +import { NOTIFICATION_TYPES } from '../../utils/enums'; const logger = getLogger('DatafileManager'); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 6ddfc54a3..c2b26478e 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -69,8 +69,6 @@ import { ODP_USER_KEY, } from '../utils/enums'; -import { BrowserOdpManager } from '../../lib/plugins/odp_manager/index.browser'; - const MODULE_NAME = 'OPTIMIZELY'; const DEFAULT_ONREADY_TIMEOUT = 30000; diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index 301879214..a60cb7cf7 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import Optimizely from '../../lib/optimizely'; +import Optimizely from '../optimizely'; import { EventTags, OptimizelyDecideOption, @@ -21,7 +21,7 @@ import { OptimizelyDecisionContext, OptimizelyForcedDecision, UserAttributes, -} from '../../lib/shared_types'; +} from '../shared_types'; import { CONTROL_ATTRIBUTES } from '../utils/enums'; import { OptimizelySegmentOption } from '../core/odp/optimizely_segment_option'; diff --git a/lib/plugins/event_processor/forwarding_event_processor.ts b/lib/plugins/event_processor/forwarding_event_processor.ts index 94b71a2ae..e528e0202 100644 --- a/lib/plugins/event_processor/forwarding_event_processor.ts +++ b/lib/plugins/event_processor/forwarding_event_processor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021-2022, Optimizely + * Copyright 2021-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import { EventProcessor, ProcessableEvent, -} from '../../../lib/modules/event_processor'; +} from '../../modules/event_processor'; import { NotificationSender } from '../../core/notification_center'; import { EventDispatcher } from '../../shared_types'; diff --git a/lib/plugins/event_processor/index.react_native.ts b/lib/plugins/event_processor/index.react_native.ts index 277512a6e..98a826d25 100644 --- a/lib/plugins/event_processor/index.react_native.ts +++ b/lib/plugins/event_processor/index.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../../lib/modules/event_processor/index.react_native'; +import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../modules/event_processor/index.react_native'; export function createEventProcessor( ...args: ConstructorParameters<typeof LogTierV1EventProcessor> diff --git a/lib/plugins/event_processor/index.ts b/lib/plugins/event_processor/index.ts index b515b7b63..836c3ce23 100644 --- a/lib/plugins/event_processor/index.ts +++ b/lib/plugins/event_processor/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020, 2022, Optimizely + * Copyright 2020, 2022-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../../lib/modules/event_processor'; +import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../modules/event_processor'; export function createEventProcessor( ...args: ConstructorParameters<typeof LogTierV1EventProcessor> diff --git a/lib/plugins/odp/event_manager/index.browser.ts b/lib/plugins/odp/event_manager/index.browser.ts index fb6c51944..37fda62a3 100644 --- a/lib/plugins/odp/event_manager/index.browser.ts +++ b/lib/plugins/odp/event_manager/index.browser.ts @@ -1,4 +1,20 @@ -import { IOdpEventManager, OdpEventManager } from '../../../../lib/core/odp/odp_event_manager'; +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IOdpEventManager, OdpEventManager } from '../../../core/odp/odp_event_manager'; import { LogLevel } from '../../../modules/logging'; import { OdpEvent } from "../../../core/odp/odp_event"; diff --git a/lib/plugins/odp/event_manager/index.node.ts b/lib/plugins/odp/event_manager/index.node.ts index 8d3aa4a8f..f2cb3277d 100644 --- a/lib/plugins/odp/event_manager/index.node.ts +++ b/lib/plugins/odp/event_manager/index.node.ts @@ -1,6 +1,22 @@ -import { OdpEvent } from '../../../../lib/core/odp/odp_event'; -import { IOdpEventManager, OdpEventManager } from '../../../../lib/core/odp/odp_event_manager'; -import { LogLevel } from '../../../../lib/modules/logging'; +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OdpEvent } from '../../../core/odp/odp_event'; +import { IOdpEventManager, OdpEventManager } from '../../../core/odp/odp_event_manager'; +import { LogLevel } from '../../../modules/logging'; const DEFAULT_BATCH_SIZE = 10; const DEFAULT_FLUSH_INTERVAL_MSECS = 1000; diff --git a/lib/plugins/odp_manager/index.node.ts b/lib/plugins/odp_manager/index.node.ts index 38d9bbdf2..7bd4d200f 100644 --- a/lib/plugins/odp_manager/index.node.ts +++ b/lib/plugins/odp_manager/index.node.ts @@ -28,7 +28,7 @@ import { } from '../../utils/enums'; import { OdpManager } from '../../core/odp/odp_manager'; -import { OdpOptions } from '../../../lib/shared_types'; +import { OdpOptions } from '../../shared_types'; import { NodeOdpEventApiManager } from '../odp/event_api_manager/index.node'; import { NodeOdpEventManager } from '../odp/event_manager/index.node'; import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 4ef0de403..68d61557b 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -19,8 +19,8 @@ * These shared type definitions include ones that will be referenced by external consumers via export_types.ts. */ -import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from '../lib/modules/logging'; -import { EventProcessor } from '../lib/modules/event_processor'; +import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; +import { EventProcessor } from './modules/event_processor'; import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; import { NOTIFICATION_TYPES } from './utils/enums'; @@ -28,7 +28,7 @@ import { NOTIFICATION_TYPES } from './utils/enums'; import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; import { ICache } from './utils/lru_cache'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; +import { RequestHandler } from './utils/http_request_handler/http'; import { OptimizelySegmentOption } from './core/odp/optimizely_segment_option'; import { IOdpSegmentApiManager } from './core/odp/odp_segment_api_manager'; import { IOdpSegmentManager } from './core/odp/odp_segment_manager'; diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index 4fbc60597..1c3f3d75d 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2017, 2019-2020, 2022, Optimizely + * Copyright 2017, 2019-2020, 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventTags } from '../../../lib/modules/event_processor'; +import { EventTags } from '../../modules/event_processor'; import { LoggerFacade } from '../../modules/logging'; import { From 9ff28873701ae023a45d23e8ccd380db7b13a397 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 23 Aug 2023 00:04:46 +0600 Subject: [PATCH 040/200] [FSSDK-9611] prepare release 5.0.0-beta4 (#857) --- CHANGELOG.md | 8 ++++++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 4 ++-- package-lock.json | 2 +- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ab59f855..ebbaf6b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.0.0-beta4] - August 22, 2023 + +### New Features +- Added support for configurable user agent parser for ODP ([#854](https://github.com/optimizely/javascript-sdk/pull/854)) + +### Bug fixes +- Fixed typescript compilation failure due to missing types ([#856](https://github.com/optimizely/javascript-sdk/pull/856)) + ## [5.0.0-beta3] - August 18, 2023 ### Bug fixes diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 691723cc1..01b91ac3a 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -192,7 +192,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta3'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta4'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 248932dc1..186f7b7f9 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta3'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta4'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index b68dcfd83..36e692d13 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta3'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta4'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index d760722e9..eaeddf6e9 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -223,8 +223,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '5.0.0-beta3'; -export const NODE_CLIENT_VERSION = '5.0.0-beta3'; +export const BROWSER_CLIENT_VERSION = '5.0.0-beta4'; +export const NODE_CLIENT_VERSION = '5.0.0-beta4'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 3b4c236f3..51f1daf15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta3", + "version": "5.0.0-beta4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 828de12c0..8b2b80601 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta3", + "version": "5.0.0-beta4", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index d1fda26fc..74304708e 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.0.0-beta3'); + expect(optlyInstance.clientVersion).toEqual('5.0.0-beta4'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From b1ef5b06ffd0fe4a7ea5339b53968235b35e5340 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 25 Aug 2023 20:44:48 +0600 Subject: [PATCH 041/200] [FSSDK-9588] export logging types and values (#858) --- lib/common_exports.ts | 19 +++++++++++++++++++ lib/index.browser.ts | 4 ++++ lib/index.lite.ts | 4 ++++ lib/index.node.ts | 4 ++++ lib/index.react_native.ts | 4 ++++ 5 files changed, 35 insertions(+) create mode 100644 lib/common_exports.ts diff --git a/lib/common_exports.ts b/lib/common_exports.ts new file mode 100644 index 000000000..6c6374a70 --- /dev/null +++ b/lib/common_exports.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2023 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { LogLevel, LogHandler, getLogger, setLogHandler } from './modules/logging'; +export { LOG_LEVEL } from './utils/enums'; +export { createLogger } from './plugins/logger'; diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 56df474a8..98905b7a2 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -30,6 +30,7 @@ import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import Optimizely from './optimizely'; import { IUserAgentParser } from './core/odp/user_agent_parser'; import { getUserAgentParser } from './plugins/odp/user_agent_parser/index.browser'; +import * as commonExports from './common_exports'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -181,7 +182,10 @@ export { getUserAgentParser, }; +export * from './common_exports'; + export default { + ...commonExports, logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, diff --git a/lib/index.lite.ts b/lib/index.lite.ts index cba4b2f16..730aab7af 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -31,6 +31,7 @@ import { createNotificationCenter } from './core/notification_center'; import { createForwardingEventProcessor } from './plugins/event_processor/forwarding_event_processor'; import { OptimizelyDecideOption, Client, ConfigLite } from './shared_types'; import { createNoOpDatafileManager } from './plugins/datafile_manager/no_op_datafile_manager'; +import * as commonExports from './common_exports'; const logger = getLogger(); setLogHandler(loggerPlugin.createLogger()); @@ -102,7 +103,10 @@ export { OptimizelyDecideOption, }; +export * from './common_exports'; + export default { + ...commonExports, logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: noOpEventDispatcher, diff --git a/lib/index.node.ts b/lib/index.node.ts index 20b977f94..168ccc287 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -26,6 +26,7 @@ import { createEventProcessor } from './plugins/event_processor'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { createHttpPollingDatafileManager } from './plugins/datafile_manager/http_polling_datafile_manager'; import { NodeOdpManager } from './plugins/odp_manager/index.node'; +import * as commonExports from './common_exports'; const logger = getLogger(); setLogLevel(LogLevel.ERROR); @@ -136,7 +137,10 @@ export { OptimizelyDecideOption, }; +export * from './common_exports'; + export default { + ...commonExports, logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 4f5019409..205f088aa 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -34,6 +34,7 @@ import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { createHttpPollingDatafileManager } from './plugins/datafile_manager/react_native_http_polling_datafile_manager'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; +import * as commonExports from './common_exports'; const logger = getLogger(); setLogHandler(loggerPlugin.createLogger()); @@ -142,7 +143,10 @@ export { OptimizelyDecideOption, }; +export * from './common_exports'; + export default { + ...commonExports, logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, From 87f26a70f80b1472671d7cc9db99b3086b4f386f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 1 Sep 2023 21:48:59 +0600 Subject: [PATCH 042/200] [FSSDK-9585] remove lib directory from package (#862) --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 8b2b80601..295b5fc6e 100644 --- a/package.json +++ b/package.json @@ -115,8 +115,7 @@ "access": "public" }, "files": [ - "dist/", - "lib/" + "dist/" ], "nyc": { "temp-dir": "coverage/raw" From f5b4b9a4f1fa02ae70b2153de3aeddb818f10e8f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 1 Sep 2023 22:41:45 +0600 Subject: [PATCH 043/200] [FSSDK-9585] prepare for release 5.0.0-beta5 (#863) --- CHANGELOG.md | 6 ++++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 4 ++-- package-lock.json | 2 +- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebbaf6b11..c8fbcb97e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.0.0-beta5] - September 1, 2023 + +### Changed +- Exported logging related types and values from the package entrypoint ([#858](https://github.com/optimizely/javascript-sdk/pull/858)) +- Removed /lib directory from the published pacakage ([#862](https://github.com/optimizely/javascript-sdk/pull/862)) + ## [5.0.0-beta4] - August 22, 2023 ### New Features diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 01b91ac3a..013bb016b 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -192,7 +192,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta4'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta5'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 186f7b7f9..8db1cf76b 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta4'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta5'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 36e692d13..6bafd0ddc 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta4'); + assert.equal(optlyInstance.clientVersion, '5.0.0-beta5'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index eaeddf6e9..30ba4d090 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -223,8 +223,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '5.0.0-beta4'; -export const NODE_CLIENT_VERSION = '5.0.0-beta4'; +export const BROWSER_CLIENT_VERSION = '5.0.0-beta5'; +export const NODE_CLIENT_VERSION = '5.0.0-beta5'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 51f1daf15..eb318216f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta4", + "version": "5.0.0-beta5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 295b5fc6e..6a135e7d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta4", + "version": "5.0.0-beta5", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 74304708e..c747c5ac9 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.0.0-beta4'); + expect(optlyInstance.clientVersion).toEqual('5.0.0-beta5'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 894398bef3ee516056838c0ea1f5adae0b2d9169 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 6 Oct 2023 01:35:36 +0600 Subject: [PATCH 044/200] [FSSDK-9605] add sendBeacon event dispatcher support (#866) --- lib/index.browser.ts | 10 + lib/modules/event_processor/eventProcessor.ts | 11 +- lib/modules/event_processor/eventQueue.ts | 116 +- .../v1/v1EventProcessor.react_native.ts | 4 +- .../event_processor/v1/v1EventProcessor.ts | 21 +- .../send_beacon_dispatcher.ts | 51 + lib/plugins/event_processor/index.ts | 2 +- lib/shared_types.ts | 2 + package-lock.json | 4815 ++++++----------- tests/sendBeaconDispatcher.spec.ts | 102 + tests/v1EventProcessor.spec.ts | 36 + 11 files changed, 1799 insertions(+), 3371 deletions(-) create mode 100644 lib/plugins/event_dispatcher/send_beacon_dispatcher.ts create mode 100644 tests/sendBeaconDispatcher.spec.ts diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 98905b7a2..6d16665b9 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -19,6 +19,7 @@ import { LocalStoragePendingEventsDispatcher } from './modules/event_processor'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './plugins/event_dispatcher/index.browser'; +import sendBeaconEventDispatcher from './plugins/event_dispatcher/send_beacon_dispatcher'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; @@ -90,6 +91,12 @@ const createInstance = function(config: Config): Client | null { eventDispatcher = config.eventDispatcher; } + let closingDispatcher = config.closingEventDispatcher; + + if (!config.eventDispatcher && !closingDispatcher && window.navigator && 'sendBeacon' in window.navigator) { + closingDispatcher = sendBeaconEventDispatcher; + } + let eventBatchSize = config.eventBatchSize; let eventFlushInterval = config.eventFlushInterval; @@ -111,6 +118,7 @@ const createInstance = function(config: Config): Client | null { const eventProcessorConfig = { dispatcher: eventDispatcher, + closingDispatcher, flushInterval: eventFlushInterval, batchSize: eventBatchSize, maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, @@ -172,6 +180,7 @@ export { loggerPlugin as logging, defaultErrorHandler as errorHandler, defaultEventDispatcher as eventDispatcher, + sendBeaconEventDispatcher, enums, setLogHandler as setLogger, setLogLevel, @@ -189,6 +198,7 @@ export default { logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, + sendBeaconEventDispatcher, enums, setLogger: setLogHandler, setLogLevel, diff --git a/lib/modules/event_processor/eventProcessor.ts b/lib/modules/event_processor/eventProcessor.ts index 9dd91bdc6..e0b31cc3a 100644 --- a/lib/modules/event_processor/eventProcessor.ts +++ b/lib/modules/event_processor/eventProcessor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,13 +57,20 @@ export function validateAndGetBatchSize(batchSize: number): number { return batchSize } -export function getQueue(batchSize: number, flushInterval: number, sink: EventQueueSink<ProcessableEvent>, batchComparator: (eventA: ProcessableEvent, eventB: ProcessableEvent) => boolean ): EventQueue<ProcessableEvent> { +export function getQueue( + batchSize: number, + flushInterval: number, + batchComparator: (eventA: ProcessableEvent, eventB: ProcessableEvent) => boolean, + sink: EventQueueSink<ProcessableEvent>, + closingSink?: EventQueueSink<ProcessableEvent> +): EventQueue<ProcessableEvent> { let queue: EventQueue<ProcessableEvent> if (batchSize > 1) { queue = new DefaultEventQueue<ProcessableEvent>({ flushInterval, maxQueueSize: batchSize, sink, + closingSink, batchComparator, }) } else { diff --git a/lib/modules/event_processor/eventQueue.ts b/lib/modules/event_processor/eventQueue.ts index 6b71d5209..ac9d2ac66 100644 --- a/lib/modules/event_processor/eventQueue.ts +++ b/lib/modules/event_processor/eventQueue.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,149 +14,149 @@ * limitations under the License. */ -import { getLogger } from '../logging' +import { getLogger } from '../logging'; // TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' +import { Managed } from './managed'; -const logger = getLogger('EventProcessor') +const logger = getLogger('EventProcessor'); -export type EventQueueSink<K> = (buffer: K[]) => Promise<any> +export type EventQueueSink<K> = (buffer: K[]) => Promise<any>; export interface EventQueue<K> extends Managed { - enqueue(event: K): void + enqueue(event: K): void; } export interface EventQueueFactory<K> { - createEventQueue(config: { - sink: EventQueueSink<K> - flushInterval: number - maxQueueSize: number - }): EventQueue<K> + createEventQueue(config: { sink: EventQueueSink<K>, flushInterval: number, maxQueueSize: number }): EventQueue<K>; } class Timer { - private timeout: number - private callback: () => void - private timeoutId?: number + private timeout: number; + private callback: () => void; + private timeoutId?: number; constructor({ timeout, callback }: { timeout: number; callback: () => void }) { - this.timeout = Math.max(timeout, 0) - this.callback = callback + this.timeout = Math.max(timeout, 0); + this.callback = callback; } start(): void { - this.timeoutId = setTimeout(this.callback, this.timeout) as any + this.timeoutId = setTimeout(this.callback, this.timeout) as any; } refresh(): void { - this.stop() - this.start() + this.stop(); + this.start(); } stop(): void { if (this.timeoutId) { - clearTimeout(this.timeoutId as any) + clearTimeout(this.timeoutId as any); } } } export class SingleEventQueue<K> implements EventQueue<K> { - private sink: EventQueueSink<K> + private sink: EventQueueSink<K>; constructor({ sink }: { sink: EventQueueSink<K> }) { - this.sink = sink + this.sink = sink; } start(): Promise<any> { // no-op - return Promise.resolve() + return Promise.resolve(); } stop(): Promise<any> { // no-op - return Promise.resolve() + return Promise.resolve(); } enqueue(event: K): void { - this.sink([event]) + this.sink([event]); } } export class DefaultEventQueue<K> implements EventQueue<K> { // expose for testing - public timer: Timer - private buffer: K[] - private maxQueueSize: number - private sink: EventQueueSink<K> + public timer: Timer; + private buffer: K[]; + private maxQueueSize: number; + private sink: EventQueueSink<K>; + private closingSink?: EventQueueSink<K>; // batchComparator is called to determine whether two events can be included // together in the same batch - private batchComparator: (eventA: K, eventB: K) => boolean - private started: boolean + private batchComparator: (eventA: K, eventB: K) => boolean; + private started: boolean; constructor({ flushInterval, maxQueueSize, sink, + closingSink, batchComparator, }: { - flushInterval: number - maxQueueSize: number - sink: EventQueueSink<K> - batchComparator: (eventA: K, eventB: K) => boolean + flushInterval: number; + maxQueueSize: number; + sink: EventQueueSink<K>; + closingSink?: EventQueueSink<K>; + batchComparator: (eventA: K, eventB: K) => boolean; }) { - this.buffer = [] - this.maxQueueSize = Math.max(maxQueueSize, 1) - this.sink = sink - this.batchComparator = batchComparator + this.buffer = []; + this.maxQueueSize = Math.max(maxQueueSize, 1); + this.sink = sink; + this.closingSink = closingSink; + this.batchComparator = batchComparator; this.timer = new Timer({ callback: this.flush.bind(this), timeout: flushInterval, - }) - this.started = false + }); + this.started = false; } start(): Promise<any> { - this.started = true + this.started = true; // dont start the timer until the first event is enqueued return Promise.resolve(); } stop(): Promise<any> { - this.started = false - const result = this.sink(this.buffer) - this.buffer = [] - this.timer.stop() - return result + this.started = false; + const result = this.closingSink ? this.closingSink(this.buffer) : this.sink(this.buffer); + this.buffer = []; + this.timer.stop(); + return result; } enqueue(event: K): void { if (!this.started) { - logger.warn('Queue is stopped, not accepting event') - return + logger.warn('Queue is stopped, not accepting event'); + return; } // If new event cannot be included into the current batch, flush so it can // be in its own new batch. - const bufferedEvent: K | undefined = this.buffer[0] + const bufferedEvent: K | undefined = this.buffer[0]; if (bufferedEvent && !this.batchComparator(bufferedEvent, event)) { - this.flush() + this.flush(); } // start the timer when the first event is put in if (this.buffer.length === 0) { - this.timer.refresh() + this.timer.refresh(); } - this.buffer.push(event) + this.buffer.push(event); if (this.buffer.length >= this.maxQueueSize) { - this.flush() + this.flush(); } } - flush() : void { - this.sink(this.buffer) - this.buffer = [] - this.timer.stop() + flush(): void { + this.sink(this.buffer); + this.buffer = []; + this.timer.stop(); } } diff --git a/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts b/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts index ebc444580..b0cc9b8b7 100644 --- a/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts +++ b/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,7 +104,7 @@ export class LogTierV1EventProcessor implements EventProcessor { flushInterval = validateAndGetFlushInterval(flushInterval) batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) + this.queue = getQueue(batchSize, flushInterval, areEventContextsEqual, this.drainQueue.bind(this)) this.pendingEventsStore = new ReactNativeEventsStore(maxQueueSize, PENDING_EVENTS_STORE_KEY) this.eventBufferStore = new ReactNativeEventsStore(maxQueueSize, EVENT_BUFFER_STORE_KEY) } diff --git a/lib/modules/event_processor/v1/v1EventProcessor.ts b/lib/modules/event_processor/v1/v1EventProcessor.ts index 6f4d6cabf..235fae83b 100644 --- a/lib/modules/event_processor/v1/v1EventProcessor.ts +++ b/lib/modules/event_processor/v1/v1EventProcessor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,31 +36,41 @@ const logger = getLogger('LogTierV1EventProcessor') export class LogTierV1EventProcessor implements EventProcessor { private dispatcher: EventDispatcher + private closingDispatcher?: EventDispatcher private queue: EventQueue<ProcessableEvent> private notificationCenter?: NotificationSender private requestTracker: RequestTracker constructor({ dispatcher, + closingDispatcher, flushInterval = DEFAULT_FLUSH_INTERVAL, batchSize = DEFAULT_BATCH_SIZE, notificationCenter, }: { dispatcher: EventDispatcher + closingDispatcher?: EventDispatcher flushInterval?: number batchSize?: number notificationCenter?: NotificationSender }) { this.dispatcher = dispatcher + this.closingDispatcher = closingDispatcher this.notificationCenter = notificationCenter this.requestTracker = new RequestTracker() flushInterval = validateAndGetFlushInterval(flushInterval) batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) + this.queue = getQueue( + batchSize, + flushInterval, + areEventContextsEqual, + this.drainQueue.bind(this, false), + this.drainQueue.bind(this, true), + ); } - drainQueue(buffer: ProcessableEvent[]): Promise<void> { + private drainQueue(useClosingDispatcher: boolean, buffer: ProcessableEvent[]): Promise<void> { const reqPromise = new Promise<void>(resolve => { logger.debug('draining queue with %s events', buffer.length) @@ -70,7 +80,10 @@ export class LogTierV1EventProcessor implements EventProcessor { } const formattedEvent = formatEvents(buffer) - this.dispatcher.dispatchEvent(formattedEvent, () => { + const dispatcher = useClosingDispatcher && this.closingDispatcher + ? this.closingDispatcher : this.dispatcher; + + dispatcher.dispatchEvent(formattedEvent, () => { resolve() }) sendEventNotification(this.notificationCenter, formattedEvent) diff --git a/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts b/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts new file mode 100644 index 000000000..4cac7b7c3 --- /dev/null +++ b/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EventDispatcher } from '../../modules/event_processor/eventDispatcher'; + +export type Event = { + url: string; + httpVerb: 'POST'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params: any; +} + +/** + * Sample event dispatcher implementation for tracking impression and conversions + * Users of the SDK can provide their own implementation + * @param {Event} eventObj + * @param {Function} callback + */ +export const dispatchEvent = function( + eventObj: Event, + callback: (response: { statusCode: number; }) => void +): void { + const { params, url } = eventObj; + const blob = new Blob([JSON.stringify(params)], { + type: "application/json", + }); + + const success = navigator.sendBeacon(url, blob); + callback({ + statusCode: success ? 200 : 500, + }); +} + +const eventDispatcher : EventDispatcher = { + dispatchEvent, +} + +export default eventDispatcher; diff --git a/lib/plugins/event_processor/index.ts b/lib/plugins/event_processor/index.ts index 836c3ce23..70f30e23a 100644 --- a/lib/plugins/event_processor/index.ts +++ b/lib/plugins/event_processor/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020, 2022-2023 Optimizely + * Copyright 2020, 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 68d61557b..d62c0b1c9 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -405,6 +405,8 @@ export interface ConfigLite { errorHandler?: ErrorHandler; // event dispatcher function eventDispatcher?: EventDispatcher; + // event dispatcher to use when closing + closingEventDispatcher?: EventDispatcher; // The object to validate against the schema jsonSchemaValidator?: { validate(jsonObject: unknown): boolean; diff --git a/package-lock.json b/package-lock.json index eb318216f..1576246c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,236 +4,348 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "requires": { - "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", - "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", + "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", "dev": true }, "@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", + "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.22.20", + "@babel/helpers": "^7.22.15", + "@babel/parser": "^7.22.16", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.20", + "@babel/types": "^7.22.19", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "dependencies": { - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", + "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", "dev": true, "requires": { - "@babel/types": "^7.19.0", + "@babel/types": "^7.22.15", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - } } }, "@babel/helper-compilation-targets": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", - "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.15" } }, "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz", + "integrity": "sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" } }, "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true }, "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true }, "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", + "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", "dev": true, "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", + "version": "7.22.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", + "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -345,39 +457,39 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", + "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.16", + "@babel/types": "^7.22.19", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -391,22 +503,14 @@ } }, "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.22.19", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", + "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.19", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - } } }, "@bcoe/v8-coverage": { @@ -421,16 +525,31 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -455,21 +574,27 @@ } } }, + "@eslint/js": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "dev": true + }, "@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" } }, - "@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true }, "@humanwhocodes/object-schema": { @@ -491,12 +616,6 @@ "resolve-from": "^5.0.0" }, "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -534,12 +653,6 @@ "p-limit": "^2.2.0" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -566,57 +679,6 @@ "jest-message-util": "^28.1.3", "jest-util": "^28.1.3", "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@jest/core": { @@ -654,143 +716,18 @@ "rimraf": "^3.0.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@jest/environment": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", - "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", "dev": true, "requires": { - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", "@types/node": "*", - "jest-mock": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "jest-mock": "^28.1.3" } }, "@jest/expect": { @@ -813,141 +750,17 @@ } }, "@jest/fake-timers": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", - "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", "dev": true, "requires": { - "@jest/types": "^29.0.1", + "@jest/types": "^28.1.3", "@sinonjs/fake-timers": "^9.1.2", "@types/node": "*", - "jest-message-util": "^29.0.1", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" } }, "@jest/globals": { @@ -959,44 +772,6 @@ "@jest/environment": "^28.1.3", "@jest/expect": "^28.1.3", "@jest/types": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } } }, "@jest/reporters": { @@ -1030,57 +805,6 @@ "strip-ansi": "^6.0.0", "terminal-link": "^2.0.0", "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@jest/schemas": { @@ -1148,57 +872,6 @@ "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@jest/types": { @@ -1213,73 +886,23 @@ "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" } }, "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true }, "@jridgewell/set-array": { @@ -1289,42 +912,29 @@ "dev": true }, "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@nodelib/fs.scandir": { @@ -1354,9 +964,9 @@ } }, "@react-native-async-storage/async-storage": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", - "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.19.3.tgz", + "integrity": "sha512-CwGfoHCWdPOTPS+2fW6YRE1fFBpT9++ahLEroX5hkgwyoQ+TkmjOaUxixdEIoVua9Pz5EF2pGOIJzqOTMWfBlA==", "dev": true, "requires": { "merge-options": "^3.0.4" @@ -1408,15 +1018,15 @@ } }, "@sinclair/typebox": { - "version": "0.24.34", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", - "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", "dev": true }, "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -1458,6 +1068,12 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -1465,31 +1081,31 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", + "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", "dev": true, "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", + "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", "dev": true, "requires": { "@babel/types": "^7.0.0" } }, "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", + "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -1497,24 +1113,18 @@ } }, "@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", + "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", "dev": true, "requires": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", - "dev": true - }, - "@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", "dev": true }, "@types/cookie": { @@ -1524,15 +1134,18 @@ "dev": true }, "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true + "version": "2.8.14", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", + "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/eslint": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", - "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", + "version": "8.44.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", + "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", "dev": true, "requires": { "@types/estree": "*", @@ -1556,9 +1169,9 @@ "dev": true }, "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", + "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", "dev": true, "requires": { "@types/node": "*" @@ -1595,9 +1208,9 @@ "dev": true }, "@types/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", "dev": true, "requires": { "@types/node": "*", @@ -1606,9 +1219,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, "@types/mocha": { @@ -1618,21 +1231,21 @@ "dev": true }, "@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.1.tgz", + "integrity": "sha512-LWDwHYO1C3YPpIQWXHeXAVih2nLsgN1Q5RamkYZRIZYfsz8HGNRji8vNhHs54LjcSgVx6AJC/6n/Q3Tn+fUb3g==", "dev": true }, "@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", + "version": "18.17.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", + "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", "dev": true }, "@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, "@types/resolve": { @@ -1644,6 +1257,12 @@ "@types/node": "*" } }, + "@types/semver": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -1651,27 +1270,27 @@ "dev": true }, "@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", + "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", "dev": true }, "@types/ua-parser-js": { - "version": "0.7.36", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", - "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", "dev": true }, "@types/uuid": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", - "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.11.tgz", + "integrity": "sha512-CJNkbEu4IdVuBMRVaNC2GjASgJK7ziqDlVXWuJ1pvhOLADl7nzxhTKjHRdOmo2SuXuygcWBmzgYgn9foTX0UiA==", "dev": true }, "@types/yargs": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", - "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -1684,69 +1303,71 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", - "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/type-utils": "5.33.0", - "@typescript-eslint/utils": "5.33.0", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "regexpp": "^3.2.0", + "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", "tsutils": "^3.21.0" } }, "@typescript-eslint/parser": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", - "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" } }, "@typescript-eslint/type-utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", - "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "requires": { - "@typescript-eslint/utils": "5.33.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1755,172 +1376,174 @@ } }, "@typescript-eslint/utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", - "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "requires": { + "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" } }, "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", "dev": true }, "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -1953,33 +1576,25 @@ } }, "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true }, "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true }, "acorn-jsx": { @@ -1989,18 +1604,18 @@ "dev": true }, "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "debug": "4" } }, "aggregate-error": { @@ -2055,28 +1670,37 @@ "dev": true }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" } }, "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, "arg": { @@ -2118,7 +1742,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true }, "assertion-error": { @@ -2130,19 +1754,19 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, "babel-jest": { @@ -2158,57 +1782,6 @@ "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "babel-plugin-istanbul": { @@ -2281,7 +1854,7 @@ "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "requires": { "tweetnacl": "^0.14.3" @@ -2300,21 +1873,21 @@ "dev": true }, "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", + "qs": "6.11.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -2328,6 +1901,15 @@ "ms": "2.0.0" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2335,9 +1917,9 @@ "dev": true }, "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dev": true, "requires": { "side-channel": "^1.0.4" @@ -2364,12 +1946,6 @@ "fill-range": "^7.0.1" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -2377,15 +1953,15 @@ "dev": true }, "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" } }, "browserstack": { @@ -2395,42 +1971,51 @@ "dev": true, "requires": { "https-proxy-agent": "^2.2.1" - } - }, - "browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" }, "dependencies": { "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "debug": "4" + "ms": "^2.1.1" } }, "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "requires": { - "agent-base": "6", - "debug": "4" + "agent-base": "^4.3.0", + "debug": "^3.1.0" } } } }, + "browserstack-local": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.4.tgz", + "integrity": "sha512-OueHCaQQutO+Fezg+ZTieRn+gdV+JocLjiAQ8nYecu08GhIt3ms79cDHfpoZmECAdoQ6OLdm7ODd+DtQzl4lrA==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + } + }, "bs-logger": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", @@ -2479,6 +2064,21 @@ "write-file-atomic": "^3.0.0" }, "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -2510,32 +2110,32 @@ "dev": true }, "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "caniuse-lite": { - "version": "1.0.30001407", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", - "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", + "version": "1.0.30001538", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", + "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", "dev": true }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", - "deep-eql": "^3.0.1", + "deep-eql": "^4.1.2", "get-func-name": "^2.0.0", "loupe": "^2.3.1", "pathval": "^1.1.1", @@ -2543,14 +2143,13 @@ } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "char-regex": { @@ -2562,7 +2161,7 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", "dev": true }, "chokidar": { @@ -2588,15 +2187,15 @@ "dev": true }, "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true }, "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "clean-stack": { @@ -2606,41 +2205,41 @@ "dev": true }, "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "requires": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "combined-stream": { @@ -2661,19 +2260,13 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "connect": { @@ -2700,33 +2293,22 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true } } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "cookie": { "version": "0.4.2", @@ -2737,7 +2319,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, "cors": { @@ -2772,17 +2354,6 @@ "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "cssom": { @@ -2811,13 +2382,13 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", "dev": true }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -2835,9 +2406,9 @@ } }, "date-format": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", - "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true }, "debug": { @@ -2852,13 +2423,13 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, "decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "decompress-response": { @@ -2876,9 +2447,9 @@ "dev": true }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -2891,15 +2462,24 @@ "dev": true }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, "depd": { @@ -2923,7 +2503,7 @@ "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true }, "diff": { @@ -2945,14 +2525,6 @@ "dev": true, "requires": { "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } } }, "doctrine": { @@ -2967,7 +2539,7 @@ "dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", "dev": true, "requires": { "custom-event": "~1.0.0", @@ -2994,7 +2566,7 @@ "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "requires": { "jsbn": "~0.1.0", @@ -3004,13 +2576,13 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, "electron-to-chromium": { - "version": "1.4.256", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", - "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", + "version": "1.4.526", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.526.tgz", + "integrity": "sha512-tjjTMjmZAx1g6COrintLTa2/jcafYKxKoiEkdQOrVdbLaHh2wCt2nsAF8ZHweezkrP+dl/VG9T5nabcYoo0U5Q==", "dev": true }, "emittery": { @@ -3028,13 +2600,13 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true }, "engine.io": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", - "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz", + "integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -3045,28 +2617,28 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" }, "dependencies": { "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "dev": true } } }, "engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", "dev": true }, "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -3076,13 +2648,13 @@ "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true }, "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, "error-ex": { @@ -3095,9 +2667,9 @@ } }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "dev": true }, "es6-error": { @@ -3115,7 +2687,7 @@ "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "dev": true, "requires": { "es6-promise": "^4.0.3" @@ -3130,25 +2702,24 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "requires": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1", "source-map": "~0.6.1" }, "dependencies": { @@ -3157,163 +2728,64 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } } } }, "eslint": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", - "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "ajv": "^6.10.0", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.3", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -3335,12 +2807,6 @@ "is-glob": "^4.0.3" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3349,45 +2815,6 @@ "requires": { "argparse": "^2.0.1" } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -3419,38 +2846,21 @@ "estraverse": "^4.1.1" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, "espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -3460,9 +2870,9 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -3514,7 +2924,7 @@ "event-stream": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", "dev": true, "requires": { "duplexer": "~0.1.1", @@ -3558,7 +2968,7 @@ "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true }, "expect": { @@ -3583,7 +2993,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true }, "fast-deep-equal": { @@ -3593,15 +3003,15 @@ "dev": true }, "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -3620,22 +3030,22 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" } }, "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "requires": { "bser": "2.1.1" @@ -3657,17 +3067,6 @@ "dev": true, "requires": { "to-regex-range": "^5.0.1" - }, - "dependencies": { - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } } }, "finalhandler": { @@ -3697,7 +3096,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, "on-finished": { @@ -3726,6 +3125,23 @@ "commondir": "^1.0.1", "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "find-up": { @@ -3736,47 +3152,29 @@ "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" - }, - "dependencies": { - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } } }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", "dev": true, "requires": { - "flatted": "^3.1.0", + "flatted": "^3.2.7", + "keyv": "^4.5.3", "rimraf": "^3.0.2" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } } }, "flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "dev": true }, "foreground-child": { @@ -3787,55 +3185,12 @@ "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true }, "form-data": { @@ -3852,7 +3207,7 @@ "formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", "dev": true, "requires": { "samsam": "1.x" @@ -3861,7 +3216,7 @@ "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", "dev": true }, "fromentries": { @@ -3873,7 +3228,7 @@ "fs-access": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", "dev": true, "requires": { "null-check": "^1.0.0" @@ -3888,18 +3243,26 @@ "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" + }, + "dependencies": { + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "optional": true }, @@ -3909,12 +3272,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3930,17 +3287,18 @@ "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", "dev": true }, "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" } }, @@ -3965,7 +3323,7 @@ "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -4001,20 +3359,12 @@ "dev": true }, "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "dev": true, "requires": { "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } } }, "globby": { @@ -4032,15 +3382,15 @@ } }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "growl": { @@ -4052,7 +3402,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "dev": true }, "har-validator": { @@ -4075,9 +3425,15 @@ } }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true }, "has-symbols": { @@ -4096,10 +3452,10 @@ "type-fest": "^0.8.0" }, "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true } } @@ -4107,7 +3463,7 @@ "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", "dev": true }, "html-encoding-sniffer": { @@ -4158,23 +3514,12 @@ "@tootallnate/once": "2", "agent-base": "6", "debug": "4" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - } } }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -4183,24 +3528,13 @@ } }, "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "agent-base": "6", + "debug": "4" } }, "human-signals": { @@ -4210,18 +3544,18 @@ "dev": true }, "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "import-fresh": { @@ -4247,7 +3581,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "indent-string": { @@ -4259,7 +3593,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -4275,7 +3609,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, "is-binary-path": { @@ -4288,9 +3622,9 @@ } }, "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -4299,7 +3633,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { @@ -4326,7 +3660,7 @@ "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", "dev": true }, "is-number": { @@ -4335,6 +3669,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -4359,7 +3699,7 @@ "is-running": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", + "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", "dev": true }, "is-stream": { @@ -4371,7 +3711,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, "is-windows": { @@ -4380,6 +3720,12 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, "isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -4389,13 +3735,13 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, "istanbul-lib-coverage": { @@ -4404,10 +3750,19 @@ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "requires": { "@babel/core": "^7.12.3", @@ -4418,9 +3773,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -4437,86 +3792,17 @@ "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^8.3.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "istanbul-lib-source-maps": { @@ -4528,20 +3814,12 @@ "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -4560,46 +3838,6 @@ "jest-cli": "^28.1.3" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "jest-cli": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", @@ -4619,36 +3857,6 @@ "prompts": "^2.0.1", "yargs": "^17.3.1" } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true } } }, @@ -4687,93 +3895,6 @@ "pretty-format": "^28.1.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-config": { @@ -4804,57 +3925,6 @@ "pretty-format": "^28.1.3", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-diff": { @@ -4867,57 +3937,6 @@ "diff-sequences": "^28.1.1", "jest-get-type": "^28.0.2", "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-docblock": { @@ -4940,91 +3959,66 @@ "jest-get-type": "^28.0.2", "jest-util": "^28.1.3", "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-environment-jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", - "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", "dev": true, "requires": { - "@jest/environment": "^29.0.1", - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", "jsdom": "^20.0.0" }, "dependencies": { + "@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + } + }, + "@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "requires": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.27.8" } }, "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "requires": { - "@jest/schemas": "^29.0.0", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -5032,53 +4026,71 @@ "chalk": "^4.0.0" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "type-detect": "4.0.8" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@sinonjs/commons": "^3.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "requires": { - "color-name": "~1.1.4" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } }, "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "requires": { - "@jest/types": "^29.0.1", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -5086,13 +4098,15 @@ "picomatch": "^2.2.3" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" } } } @@ -5109,44 +4123,6 @@ "@types/node": "*", "jest-mock": "^28.1.3", "jest-util": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } } }, "jest-get-type": { @@ -5186,9 +4162,9 @@ } }, "jest-localstorage-mock": { - "version": "2.4.22", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", - "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", + "version": "2.4.26", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz", + "integrity": "sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==", "dev": true }, "jest-matcher-utils": { @@ -5201,57 +4177,6 @@ "jest-diff": "^28.1.3", "jest-get-type": "^28.0.2", "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-message-util": { @@ -5269,147 +4194,22 @@ "pretty-format": "^28.1.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-mock": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", - "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", "dev": true, "requires": { - "@jest/types": "^29.0.3", + "@jest/types": "^28.1.3", "@types/node": "*" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", - "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true }, "jest-regex-util": { @@ -5433,57 +4233,6 @@ "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-resolve-dependencies": { @@ -5503,230 +4252,56 @@ "dev": true, "requires": { "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + } + }, + "jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" } }, "jest-snapshot": { @@ -5758,57 +4333,6 @@ "natural-compare": "^1.4.0", "pretty-format": "^28.1.3", "semver": "^7.3.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-util": { @@ -5823,57 +4347,6 @@ "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-validate": { @@ -5890,54 +4363,11 @@ "pretty-format": "^28.1.3" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -5955,57 +4385,6 @@ "emittery": "^0.10.2", "jest-util": "^28.1.3", "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-worker": { @@ -6019,12 +4398,6 @@ "supports-color": "^8.0.0" }, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -6055,22 +4428,22 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, "jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "requires": { "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", + "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", @@ -6078,30 +4451,20 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", - "ws": "^8.8.0", + "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -6113,20 +4476,10 @@ "mime-types": "^2.1.12" } }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "requires": { "psl": "^1.1.33", @@ -6134,15 +4487,21 @@ "universalify": "^0.2.0", "url-parse": "^1.5.3" } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true } } }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "json-loader": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", @@ -6169,25 +4528,25 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "requires": { "graceful-fs": "^4.1.6" @@ -6212,9 +4571,9 @@ "dev": true }, "karma": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", - "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", + "integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==", "dev": true, "requires": { "@colors/colors": "1.5.0", @@ -6243,34 +4602,42 @@ "yargs": "^16.1.1" }, "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "glob": "^7.1.3" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "ua-parser-js": { + "version": "0.7.36", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.36.tgz", + "integrity": "sha512-CPPLoCts2p7D8VbybttE3P2ylv0OBZEAy7a12DsulIEcAiMtWJy+PBgMXgWDI80D5UwqE8oQPHYnk13tm38M2Q==", "dev": true }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "rimraf": "^3.0.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, - "ua-parser-js": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", - "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true } } @@ -6289,7 +4656,7 @@ "karma-chai": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", + "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", "dev": true }, "karma-chrome-launcher": { @@ -6300,12 +4667,23 @@ "requires": { "fs-access": "^1.0.0", "which": "^1.2.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "karma-mocha": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=", + "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", "dev": true, "requires": { "minimist": "1.2.0" @@ -6314,7 +4692,7 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", "dev": true } } @@ -6330,6 +4708,15 @@ "webpack-merge": "^4.1.5" } }, + "keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6339,7 +4726,7 @@ "lcov-parse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", "dev": true }, "leven": { @@ -6388,7 +4775,7 @@ "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, "lodash.memoize": { @@ -6410,28 +4797,31 @@ "dev": true }, "log4js": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", - "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, "requires": { - "date-format": "^4.0.13", + "date-format": "^4.0.14", "debug": "^4.3.4", - "flatted": "^3.2.6", + "flatted": "^3.2.7", "rfdc": "^1.3.0", - "streamroller": "^3.1.2" + "streamroller": "^3.1.5" } }, "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", - "dev": true + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } }, "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", "dev": true, "requires": { "get-func-name": "^2.0.0" @@ -6456,20 +4846,12 @@ } }, "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "semver": "^7.5.3" } }, "make-error": { @@ -6490,13 +4872,13 @@ "map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, "merge-options": { @@ -6572,9 +4954,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "mkdirp": { @@ -6614,6 +4996,12 @@ "ms": "2.0.0" } }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -6628,6 +5016,12 @@ "path-is-absolute": "^1.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -6640,13 +5034,13 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", "dev": true, "requires": { "minimist": "0.0.8" @@ -6655,7 +5049,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, "supports-color": { @@ -6672,7 +5066,7 @@ "mocha-lcov-reporter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", + "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", "dev": true }, "ms": { @@ -6689,13 +5083,19 @@ "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", "dev": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, "negotiator": { @@ -6721,17 +5121,6 @@ "just-extend": "^4.0.2", "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - } } }, "nock": { @@ -6750,7 +5139,7 @@ "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, "node-preload": { @@ -6763,9 +5152,9 @@ } }, "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "normalize-path": { @@ -6786,13 +5175,13 @@ "null-check": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", "dev": true }, "nwsapi": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", - "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", "dev": true }, "nyc": { @@ -6830,30 +5219,6 @@ "yargs": "^15.0.2" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "requires": { - "default-require-extensions": "^3.0.0" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -6865,30 +5230,6 @@ "wrap-ansi": "^6.2.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, - "requires": { - "strip-bom": "^4.0.0" - } - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -6899,15 +5240,6 @@ "path-exists": "^4.0.0" } }, - "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "requires": { - "append-transform": "^2.0.0" - } - }, "istanbul-lib-instrument": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", @@ -6929,6 +5261,15 @@ "p-locate": "^4.1.0" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -6947,31 +5288,16 @@ "p-limit": "^2.2.0" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "wrap-ansi": { @@ -7031,13 +5357,13 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true }, "on-finished": { @@ -7052,7 +5378,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "requires": { "wrappy": "1" @@ -7068,17 +5394,17 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "p-limit": { @@ -7148,12 +5474,12 @@ } }, "parse5": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", - "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dev": true, "requires": { - "entities": "^4.3.0" + "entities": "^4.4.0" } }, "parseurl": { @@ -7162,10 +5488,16 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "path-key": { @@ -7187,16 +5519,14 @@ "dev": true, "requires": { "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } } }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -7206,7 +5536,7 @@ "pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", "dev": true, "requires": { "through": "~2.3" @@ -7215,7 +5545,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, "picocolors": { @@ -7231,9 +5561,9 @@ "dev": true }, "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true }, "pkg-dir": { @@ -7281,12 +5611,6 @@ "requires": { "p-limit": "^2.2.0" } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true } } }, @@ -7378,15 +5702,15 @@ "dev": true }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", "dev": true }, "qjobs": { @@ -7429,15 +5753,26 @@ "dev": true }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "react-is": { @@ -7455,16 +5790,10 @@ "picomatch": "^2.2.1" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "requires": { "es6-error": "^4.0.1" @@ -7509,7 +5838,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, "require-main-filename": { @@ -7521,16 +5850,16 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", "dev": true, "requires": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -7559,9 +5888,9 @@ "dev": true }, "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", "dev": true }, "reusify": { @@ -7616,6 +5945,12 @@ "terser": "^4.6.2" }, "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, "jest-worker": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", @@ -7721,9 +6056,9 @@ } }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "requires": { "@types/json-schema": "^7.0.8", @@ -7732,9 +6067,9 @@ } }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -7752,7 +6087,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, "setprototypeof": { @@ -7807,6 +6142,14 @@ "samsam": "^1.1.3", "text-encoding": "0.6.4", "type-detect": "^4.0.0" + }, + "dependencies": { + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", + "dev": true + } } }, "sisteransi": { @@ -7822,33 +6165,44 @@ "dev": true }, "socket.io": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", - "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", + "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.2.0", - "socket.io-adapter": "~2.4.0", - "socket.io-parser": "~4.0.4" + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" } }, "socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", - "dev": true + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "requires": { + "ws": "~8.11.0" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true + } + } }, "socket.io-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", - "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, @@ -7888,30 +6242,27 @@ "which": "^2.0.1" }, "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "glob": "^7.1.3" + "semver": "^6.0.0" } }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true } } }, "split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", "dev": true, "requires": { "through": "2" @@ -7920,7 +6271,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "sshpk": { @@ -7941,9 +6292,9 @@ } }, "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "requires": { "escape-string-regexp": "^2.0.0" @@ -7966,19 +6317,19 @@ "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", "dev": true, "requires": { "duplexer": "~0.1.1" } }, "streamroller": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.2.tgz", - "integrity": "sha512-wZswqzbgGGsXYIrBYhOE0yP+nQ6XRk7xDcYwuQAGTYXdyAUmvgVFE0YU1g5pvQT0m7GBaQfYcSnlHbapuK0H0A==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, "requires": { - "date-format": "^4.0.13", + "date-format": "^4.0.14", "debug": "^4.3.4", "fs-extra": "^8.1.0" } @@ -8032,12 +6383,12 @@ "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, "supports-hyperlinks": { @@ -8048,23 +6399,6 @@ "requires": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "supports-preserve-symlinks-flag": { @@ -8088,7 +6422,7 @@ "temp-fs": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", + "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", "dev": true, "requires": { "rimraf": "~2.5.2" @@ -8097,7 +6431,7 @@ "rimraf": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", "dev": true, "requires": { "glob": "^7.0.5" @@ -8131,36 +6465,20 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } } } }, "terser-webpack-plugin": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", - "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.7", + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.7.2" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" }, "dependencies": { "commander": { @@ -8169,12 +6487,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -8187,20 +6499,14 @@ } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "requires": { "randombytes": "^2.1.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -8221,13 +6527,13 @@ } }, "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz", + "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==", "dev": true, "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" } @@ -8248,27 +6554,51 @@ "text-encoding": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", "dev": true }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -8308,77 +6638,18 @@ "make-error": "1.x", "semver": "7.x", "yargs-parser": "^21.0.1" - }, - "dependencies": { - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } } }, "ts-loader": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.1.tgz", - "integrity": "sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "requires": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "ts-mockito": { @@ -8409,12 +6680,6 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -8428,9 +6693,9 @@ } }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, "tsutils": { @@ -8453,7 +6718,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -8462,7 +6727,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true }, "type-check": { @@ -8481,9 +6746,9 @@ "dev": true }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, "type-is": { @@ -8506,32 +6771,32 @@ } }, "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "ua-parser-js": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", - "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==" + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.36.tgz", + "integrity": "sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==" }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true }, "update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "requires": { "escalade": "^3.1.1", @@ -8560,7 +6825,7 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true }, "uuid": { @@ -8568,16 +6833,10 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.12", @@ -8594,7 +6853,7 @@ "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -8605,22 +6864,13 @@ "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", "dev": true }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, "w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, "requires": { "xml-name-validator": "^4.0.0" @@ -8652,22 +6902,22 @@ "dev": true }, "webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", + "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -8676,17 +6926,17 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true } } @@ -8713,17 +6963,6 @@ "dev": true, "requires": { "iconv-lite": "0.6.3" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } } }, "whatwg-mimetype": { @@ -8743,24 +6982,18 @@ } }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, "wrap-ansi": { @@ -8772,38 +7005,12 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "write-file-atomic": { @@ -8817,9 +7024,9 @@ } }, "ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "dev": true }, "xml-name-validator": { @@ -8847,24 +7054,24 @@ "dev": true }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" } }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true }, "yn": { diff --git a/tests/sendBeaconDispatcher.spec.ts b/tests/sendBeaconDispatcher.spec.ts new file mode 100644 index 000000000..743d8dae6 --- /dev/null +++ b/tests/sendBeaconDispatcher.spec.ts @@ -0,0 +1,102 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import sendBeaconDispatcher, { Event } from '../lib/plugins/event_dispatcher/send_beacon_dispatcher'; +import { anyString, anything, capture, instance, mock, reset, when } from 'ts-mockito'; + +describe('dispatchEvent', function() { + const mockNavigator = mock<Navigator>(); + + afterEach(function() { + reset(mockNavigator); + }); + + it('should call sendBeacon with correct url, data and type', async () => { + var eventParams = { testParam: 'testParamValue' }; + var eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(true); + const navigator = instance(mockNavigator); + global.navigator.sendBeacon = navigator.sendBeacon; + + sendBeaconDispatcher.dispatchEvent(eventObj, () => {}); + + const [url, data] = capture(mockNavigator.sendBeacon).last(); + const blob = data as Blob; + + const reader = new FileReader(); + reader.readAsBinaryString(blob); + + const sentParams = await new Promise((resolve) => { + reader.onload = () => { + resolve(reader.result); + }; + }); + + + expect(url).toEqual(eventObj.url); + expect(blob.type).toEqual('application/json'); + expect(sentParams).toEqual(JSON.stringify(eventObj.params)); + }); + + it('should call call callback with status 200 on sendBeacon success', (done) => { + var eventParams = { testParam: 'testParamValue' }; + var eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(true); + const navigator = instance(mockNavigator); + global.navigator.sendBeacon = navigator.sendBeacon; + + sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { + try { + expect(res.statusCode).toEqual(200); + done(); + } catch(err) { + done(err); + } + }); + }); + + it('should call call callback with status 200 on sendBeacon failure', (done) => { + var eventParams = { testParam: 'testParamValue' }; + var eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(false); + const navigator = instance(mockNavigator); + global.navigator.sendBeacon = navigator.sendBeacon; + + sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { + try { + expect(res.statusCode).toEqual(500); + done(); + } catch(err) { + done(err); + } + }); + }); +}); diff --git a/tests/v1EventProcessor.spec.ts b/tests/v1EventProcessor.spec.ts index 59f6cdb79..18cc93463 100644 --- a/tests/v1EventProcessor.spec.ts +++ b/tests/v1EventProcessor.spec.ts @@ -295,6 +295,42 @@ describe('LogTierV1EventProcessor', () => { await stopPromise expect(stopPromiseResolved).toBe(true) }) + + it('should use the provided closingDispatcher to dispatch events on stop', async () => { + const dispatcher = { + dispatchEvent: jest.fn(), + } + + const closingDispatcher = { + dispatchEvent: jest.fn(), + } + + const processor = new LogTierV1EventProcessor({ + dispatcher, + closingDispatcher, + flushInterval: 100000, + batchSize: 20, + }); + + processor.start() + + const events = []; + + for (let i = 0; i < 4; i++) { + const event = createImpressionEvent(); + processor.process(event); + events.push(event); + } + + processor.stop(); + jest.runAllTimers(); + + expect(dispatcher.dispatchEvent).not.toHaveBeenCalled(); + expect(closingDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + + const [data] = closingDispatcher.dispatchEvent.mock.calls[0]; + expect(data.params).toEqual(makeBatchedEventV1(events)); + }) }) describe('when batchSize = 1', () => { From d8daa0caa06439f331a777be21312f8bcfddac3e Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 12 Oct 2023 01:58:52 +0600 Subject: [PATCH 045/200] [FSSDK-9605] update CHANGELOG.md for release 4.10.0 (#878) --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8fbcb97e..990a47eb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [4.10.0] - October 11, 2023 + +### New Features +- Add support for configurable closing event dispatcher, and dispatching events using sendBeacon in the browser on instance close ([#876](https://github.com/optimizely/javascript-sdk/pull/876), [#874](https://github.com/optimizely/javascript-sdk/pull/874), [#873](https://github.com/optimizely/javascript-sdk/pull/873)) + ## [5.0.0-beta5] - September 1, 2023 ### Changed From fc1efa088298569f1001eb07a0eae7cb09e83314 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 4 Dec 2023 22:49:23 +0600 Subject: [PATCH 046/200] [FSSDK-9778] return last experiment when duplicate key in config (#881) --- lib/core/optimizely_config/index.tests.js | 23 +- lib/core/optimizely_config/index.ts | 81 ++++--- .../project_config/project_config_manager.ts | 2 +- lib/tests/test_data.js | 203 ++++++++++++++++++ 4 files changed, 275 insertions(+), 34 deletions(-) diff --git a/lib/core/optimizely_config/index.tests.js b/lib/core/optimizely_config/index.tests.js index 25ce515fe..9f147c1b5 100644 --- a/lib/core/optimizely_config/index.tests.js +++ b/lib/core/optimizely_config/index.tests.js @@ -15,6 +15,7 @@ */ import { assert } from 'chai'; import { cloneDeep } from 'lodash'; +import sinon from 'sinon'; import { createOptimizelyConfig, OptimizelyConfig } from './'; import { createProjectConfig } from '../project_config'; @@ -22,7 +23,8 @@ import { getTestProjectConfigWithFeatures, getTypedAudiencesConfig, getSimilarRuleKeyConfig, - getSimilarExperimentKeyConfig + getSimilarExperimentKeyConfig, + getDuplicateExperimentKeyConfig, } from '../../tests/test_data'; var datafile = getTestProjectConfigWithFeatures(); @@ -53,6 +55,7 @@ describe('lib/core/optimizely_config', function() { var projectSimilarRuleKeyConfigObject; var optimizelySimilarExperimentkeyConfigObject; var projectSimilarExperimentKeyConfigObject; + beforeEach(function() { projectConfigObject = createProjectConfig(cloneDeep(datafile)); optimizelyConfigObject = createOptimizelyConfig(projectConfigObject, JSON.stringify(datafile)); @@ -85,6 +88,24 @@ describe('lib/core/optimizely_config', function() { }); }); + it('should keep the last experiment in case of duplicate key and log a warning', function() { + const datafile = getDuplicateExperimentKeyConfig(); + const configObj = createProjectConfig(datafile, JSON.stringify(datafile)); + + const logger = { + warn: sinon.spy(), + } + + const optimizelyConfig = createOptimizelyConfig(configObj, JSON.stringify(datafile), logger); + const experimentsMap = optimizelyConfig.experimentsMap; + + const duplicateKey = 'experiment_rule'; + const lastExperiment = datafile.experiments[datafile.experiments.length - 1]; + + assert.equal(experimentsMap['experiment_rule'].id, lastExperiment.id); + assert.isTrue(logger.warn.calledWithExactly(`Duplicate experiment keys found in datafile: ${duplicateKey}`)); + }); + it('should return all the feature flags', function() { var featureFlagsCount = Object.keys(optimizelyConfigObject.featuresMap).length; assert.equal(featureFlagsCount, 9); diff --git a/lib/core/optimizely_config/index.ts b/lib/core/optimizely_config/index.ts index 8ea70ecce..4b435b830 100644 --- a/lib/core/optimizely_config/index.ts +++ b/lib/core/optimizely_config/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { LoggerFacade, getLogger } from '../../modules/logging'; import { ProjectConfig } from '../project_config'; import { DEFAULT_OPERATOR_TYPES } from '../condition_tree_evaluator'; import { @@ -61,7 +62,8 @@ export class OptimizelyConfig { public events: OptimizelyEvent[]; private datafile: string; - constructor(configObj: ProjectConfig, datafile: string) { + + constructor(configObj: ProjectConfig, datafile: string, logger?: LoggerFacade) { this.sdkKey = configObj.sdkKey ?? ''; this.environmentKey = configObj.environmentKey ?? ''; this.attributes = configObj.attributes; @@ -76,10 +78,12 @@ export class OptimizelyConfig { const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj); - const experimentsMapById = OptimizelyConfig.getExperimentsMapById( - configObj, featureIdVariablesMap, variableIdMap + const { experimentsMapById, experimentsMapByKey } = OptimizelyConfig.getExperimentsMap( + configObj, featureIdVariablesMap, variableIdMap, logger, ); - this.experimentsMap = OptimizelyConfig.getExperimentsKeyMap(experimentsMapById); + + this.experimentsMap = experimentsMapByKey; + this.featuresMap = OptimizelyConfig.getFeaturesMap( configObj, featureIdVariablesMap, experimentsMapById, variableIdMap ); @@ -347,39 +351,52 @@ export class OptimizelyConfig { * @param {ProjectConfig} configObj * @param {FeatureVariablesMap} featureIdVariableMap * @param {{[id: string]: FeatureVariable}} variableIdMap - * @returns {[id: string]: OptimizelyExperiment} Experiments mapped by id + * @returns { experimentsMapById: { [id: string]: OptimizelyExperiment }, experimentsMapByKey: OptimizelyExperimentsMap } Experiments mapped by id and key */ - static getExperimentsMapById( + static getExperimentsMap( configObj: ProjectConfig, featureIdVariableMap: FeatureVariablesMap, - variableIdMap: {[id: string]: FeatureVariable} - ): { [id: string]: OptimizelyExperiment } { + variableIdMap: {[id: string]: FeatureVariable}, + logger?: LoggerFacade, + ) : { experimentsMapById: { [id: string]: OptimizelyExperiment }, experimentsMapByKey: OptimizelyExperimentsMap } { const rolloutExperimentIds = this.getRolloutExperimentIds(configObj.rollouts); - const experiments = configObj.experiments; + const experimentsMapById: { [id : string]: OptimizelyExperiment } = {}; + const experimentsMapByKey: OptimizelyExperimentsMap = {}; - return (experiments || []).reduce((experimentsMap: { [id: string]: OptimizelyExperiment }, experiment) => { - if (rolloutExperimentIds.indexOf(experiment.id) === -1) { - const featureIds = configObj.experimentFeatureMap[experiment.id]; - let featureId = ''; - if (featureIds && featureIds.length > 0) { - featureId = featureIds[0]; - } - const variationsMap = OptimizelyConfig.getVariationsMap( - experiment.variations, - featureIdVariableMap, - variableIdMap, - featureId.toString() - ); - experimentsMap[experiment.id] = { - id: experiment.id, - key: experiment.key, - audiences: OptimizelyConfig.getExperimentAudiences(experiment, configObj), - variationsMap: variationsMap, - }; + const experiments = configObj.experiments || []; + experiments.forEach((experiment) => { + if (rolloutExperimentIds.indexOf(experiment.id) !== -1) { + return; } - return experimentsMap; - }, {}); + + const featureIds = configObj.experimentFeatureMap[experiment.id]; + let featureId = ''; + if (featureIds && featureIds.length > 0) { + featureId = featureIds[0]; + } + const variationsMap = OptimizelyConfig.getVariationsMap( + experiment.variations, + featureIdVariableMap, + variableIdMap, + featureId.toString() + ); + + const optimizelyExperiment: OptimizelyExperiment = { + id: experiment.id, + key: experiment.key, + audiences: OptimizelyConfig.getExperimentAudiences(experiment, configObj), + variationsMap: variationsMap, + }; + + experimentsMapById[experiment.id] = optimizelyExperiment; + if (experimentsMapByKey[experiment.key] && logger) { + logger.warn(`Duplicate experiment keys found in datafile: ${experiment.key}`); + } + experimentsMapByKey[experiment.key] = optimizelyExperiment; + }); + + return { experimentsMapById, experimentsMapByKey }; } /** @@ -461,6 +478,6 @@ export class OptimizelyConfig { * @param {string} datafile * @returns {OptimizelyConfig} An instance of OptimizelyConfig */ -export function createOptimizelyConfig(configObj: ProjectConfig, datafile: string): OptimizelyConfig { - return new OptimizelyConfig(configObj, datafile); +export function createOptimizelyConfig(configObj: ProjectConfig, datafile: string, logger?: LoggerFacade): OptimizelyConfig { + return new OptimizelyConfig(configObj, datafile, logger); } diff --git a/lib/core/project_config/project_config_manager.ts b/lib/core/project_config/project_config_manager.ts index 21026679d..0432b5fc1 100644 --- a/lib/core/project_config/project_config_manager.ts +++ b/lib/core/project_config/project_config_manager.ts @@ -210,7 +210,7 @@ export class ProjectConfigManager { */ getOptimizelyConfig(): OptimizelyConfig | null { if (!this.optimizelyConfigObj && this.configObj) { - this.optimizelyConfigObj = createOptimizelyConfig(this.configObj, toDatafile(this.configObj)); + this.optimizelyConfigObj = createOptimizelyConfig(this.configObj, toDatafile(this.configObj), logger); } return this.optimizelyConfigObj; } diff --git a/lib/tests/test_data.js b/lib/tests/test_data.js index 4ac9a1b37..e2d196967 100644 --- a/lib/tests/test_data.js +++ b/lib/tests/test_data.js @@ -3951,6 +3951,208 @@ export var getSimilarExperimentKeyConfig = function() { return cloneDeep(similarExperimentKeysConfig); }; +const duplicateExperimentKeyConfig = { + "accountId": "23793010390", + "projectId": "24812320344", + "revision": "24", + "attributes": [ + { + "id": "24778491463", + "key": "country" + }, + { + "id": "24802951640", + "key": "likes_donuts" + } + ], + "audiences": [ + { + "name": "mutext_feat", + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + "id": "24837020039" + }, + { + "id": "$opt_dummy_audience", + "name": "Optimizely-Generated Audience for Backwards Compatibility", + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]" + } + ], + "version": "4", + "events": [], + "integrations": [], + "anonymizeIP": true, + "botFiltering": false, + "typedAudiences": [ + { + "name": "mutext_feat", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "match": "exact", + "name": "country", + "type": "custom_attribute", + "value": "US" + } + ], + [ + "or", + { + "match": "exact", + "name": "likes_donuts", + "type": "custom_attribute", + "value": true + } + ] + ] + ], + "id": "24837020039" + } + ], + "variables": [], + "environmentKey": "production", + "sdkKey": "BBhivmjEBF1KLK8HkMrvj", + "featureFlags": [ + { + "id": "101043", + "key": "mutext_feat2", + "rolloutId": "rollout-101043-24783691394", + "experimentIds": [ + "9300000361925" + ], + "variables": [] + }, + { + "id": "101044", + "key": "mutex_feat", + "rolloutId": "rollout-101044-24783691394", + "experimentIds": [ + "9300000365056" + ], + "variables": [] + } + ], + "rollouts": [ + { + "id": "rollout-101043-24783691394", + "experiments": [ + { + "id": "default-rollout-101043-24783691394", + "key": "default-rollout-101043-24783691394", + "status": "Running", + "layerId": "rollout-101043-24783691394", + "variations": [ + { + "id": "321340", + "key": "off", + "featureEnabled": false, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321340", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [], + "audienceConditions": [] + } + ] + }, + { + "id": "rollout-101044-24783691394", + "experiments": [ + { + "id": "default-rollout-101044-24783691394", + "key": "default-rollout-101044-24783691394", + "status": "Running", + "layerId": "rollout-101044-24783691394", + "variations": [ + { + "id": "321343", + "key": "off", + "featureEnabled": false, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321343", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [], + "audienceConditions": [] + } + ] + } + ], + "experiments": [ + { + "id": "9300000361925", + "key": "experiment_rule", + "status": "Running", + "layerId": "9300000284731", + "variations": [ + { + "id": "321342", + "key": "variation_1", + "featureEnabled": true, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321342", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [ + "24837020039" + ], + "audienceConditions": [ + "or", + "24837020039" + ] + }, + { + "id": "9300000365056", + "key": "experiment_rule", + "status": "Running", + "layerId": "9300000287826", + "variations": [ + { + "id": "321345", + "key": "variation_1", + "featureEnabled": true, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321345", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [], + "audienceConditions": [] + } + ], + "groups": [] +}; + +export const getDuplicateExperimentKeyConfig = function() { + return cloneDeep(duplicateExperimentKeyConfig); +}; + export default { getTestProjectConfig: getTestProjectConfig, getTestDecideProjectConfig: getTestDecideProjectConfig, @@ -3966,4 +4168,5 @@ export default { getMutexFeatureTestsConfig: getMutexFeatureTestsConfig, getSimilarRuleKeyConfig: getSimilarRuleKeyConfig, getSimilarExperimentKeyConfig: getSimilarExperimentKeyConfig, + getDuplicateExperimentKeyConfig: getDuplicateExperimentKeyConfig, }; From 01b099b330eb40f868d04bc9d379d186dc431e3c Mon Sep 17 00:00:00 2001 From: Mandy Trinh <mandy.trinh@optimizely.com> Date: Fri, 8 Dec 2023 12:01:42 -0800 Subject: [PATCH 047/200] FSSDK-9843: Add infinity check (#882) --- lib/utils/event_tag_utils/index.ts | 60 +++++++++++++----------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index 1c3f3d75d..7917f3baa 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -36,26 +36,21 @@ const VALUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.VALUE; * @return {number|null} */ export function getRevenueValue(eventTags: EventTags, logger: LoggerFacade): number | null { - if (eventTags.hasOwnProperty(REVENUE_EVENT_METRIC_NAME)) { - const rawValue = eventTags[REVENUE_EVENT_METRIC_NAME]; - let parsedRevenueValue; - if (typeof rawValue === 'string') { - parsedRevenueValue = parseInt(rawValue); - if (isNaN(parsedRevenueValue)) { - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_REVENUE, MODULE_NAME, rawValue); - return null; - } - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); - return parsedRevenueValue; - } - if (typeof rawValue === 'number') { - parsedRevenueValue = rawValue; - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); - return parsedRevenueValue; - } + const rawValue = eventTags[REVENUE_EVENT_METRIC_NAME]; + + if (rawValue == null) { // null or undefined event values + return null; + } + + const parsedRevenueValue = typeof rawValue === 'string' ? parseInt(rawValue) : rawValue; + + if (isFinite(parsedRevenueValue)) { + logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); + return parsedRevenueValue; + } else { // NaN, +/- infinity values + logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_REVENUE, MODULE_NAME, rawValue); return null; } - return null; } /** @@ -65,24 +60,19 @@ export function getRevenueValue(eventTags: EventTags, logger: LoggerFacade): num * @return {number|null} */ export function getEventValue(eventTags: EventTags, logger: LoggerFacade): number | null { - if (eventTags.hasOwnProperty(VALUE_EVENT_METRIC_NAME)) { - const rawValue = eventTags[VALUE_EVENT_METRIC_NAME]; - let parsedEventValue; - if (typeof rawValue === 'string') { - parsedEventValue = parseFloat(rawValue); - if (isNaN(parsedEventValue)) { - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_VALUE, MODULE_NAME, rawValue); - return null; - } + const rawValue = eventTags[VALUE_EVENT_METRIC_NAME]; + + if (rawValue == null) { // null or undefined event values + return null; + } + + const parsedEventValue = typeof rawValue === 'string' ? parseFloat(rawValue) : rawValue; + + if (isFinite(parsedEventValue)) { logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_NUMERIC_VALUE, MODULE_NAME, parsedEventValue); return parsedEventValue; - } - if (typeof rawValue === 'number') { - parsedEventValue = rawValue; - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_NUMERIC_VALUE, MODULE_NAME, parsedEventValue); - return parsedEventValue; - } + } else { // NaN, +/- infinity values + logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_VALUE, MODULE_NAME, rawValue); return null; } - return null; -} +} \ No newline at end of file From 2b4019382fa4340426a836e7a8c1b800e44ab0d4 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Sat, 23 Dec 2023 00:12:32 +0600 Subject: [PATCH 048/200] [FSSDK-8284] Add allowed types to UserAttributes (#886) --- lib/core/custom_attribute_condition_evaluator/index.ts | 8 ++++---- lib/core/decision_service/index.ts | 2 +- lib/core/event_builder/event_helpers.ts | 2 +- lib/core/event_builder/index.ts | 2 +- lib/export_types.ts | 1 + lib/optimizely_user_context/index.ts | 3 ++- lib/shared_types.ts | 6 +++--- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts index 775be8ad7..a887a2633 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/lib/core/custom_attribute_condition_evaluator/index.ts @@ -241,7 +241,7 @@ function greaterThanEvaluator(condition: Condition, user: OptimizelyUserContext) if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { return null; } - return userValue > conditionValue; + return userValue! > conditionValue; } /** @@ -262,7 +262,7 @@ function greaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserC return null; } - return userValue >= conditionValue; + return userValue! >= conditionValue; } /** @@ -283,7 +283,7 @@ function lessThanEvaluator(condition: Condition, user: OptimizelyUserContext): b return null; } - return userValue < conditionValue; + return userValue! < conditionValue; } /** @@ -304,7 +304,7 @@ function lessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserCont return null; } - return userValue <= conditionValue; + return userValue! <= conditionValue; } /** diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 2c126c04a..28f97a09e 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -766,7 +766,7 @@ export class DecisionService { attributes.hasOwnProperty(CONTROL_ATTRIBUTES.BUCKETING_ID) ) { if (typeof attributes[CONTROL_ATTRIBUTES.BUCKETING_ID] === 'string') { - bucketingId = attributes[CONTROL_ATTRIBUTES.BUCKETING_ID]; + bucketingId = String(attributes[CONTROL_ATTRIBUTES.BUCKETING_ID]); this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.VALID_BUCKETING_ID, MODULE_NAME, bucketingId); } else { this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.BUCKETING_ID_NOT_STRING, MODULE_NAME); diff --git a/lib/core/event_builder/event_helpers.ts b/lib/core/event_builder/event_helpers.ts index e6f57fdbd..071a1427a 100644 --- a/lib/core/event_builder/event_helpers.ts +++ b/lib/core/event_builder/event_helpers.ts @@ -245,7 +245,7 @@ function buildVisitorAttributes( builtAttributes.push({ entityId: attributeId, key: attributeKey, - value: attributes[attributeKey], + value: attributeValue!, }); } } diff --git a/lib/core/event_builder/index.ts b/lib/core/event_builder/index.ts index 093994aa0..cd6781529 100644 --- a/lib/core/event_builder/index.ts +++ b/lib/core/event_builder/index.ts @@ -157,7 +157,7 @@ function getCommonEventParams({ entity_id: attributeId, key: attributeKey, type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: attributes[attributeKey], + value: attributeValue!, }); } } diff --git a/lib/export_types.ts b/lib/export_types.ts index a41272060..cfd498591 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -19,6 +19,7 @@ */ export { + UserAttributeValue, UserAttributes, OptimizelyConfig, OptimizelyVariable, diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index a60cb7cf7..96f6bc3c2 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -20,6 +20,7 @@ import { OptimizelyDecision, OptimizelyDecisionContext, OptimizelyForcedDecision, + UserAttributeValue, UserAttributes, } from '../shared_types'; import { CONTROL_ATTRIBUTES } from '../utils/enums'; @@ -80,7 +81,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { * @param {string} key An attribute key * @param {any} value An attribute value */ - setAttribute(key: string, value: unknown): void { + setAttribute(key: string, value: UserAttributeValue): void { this.attributes[key] = value; } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index d62c0b1c9..5caf705f9 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -55,10 +55,10 @@ export interface DecisionResponse<T> { readonly reasons: (string | number)[][]; } +export type UserAttributeValue = string | number | boolean | null; + export type UserAttributes = { - // TODO[OASIS-6649]: Don't use any type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [name: string]: any; + [name: string]: UserAttributeValue; }; export interface ExperimentBucketMap { From 7e0364737648fedf635105192c6e37245dcaf6e1 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 2 Jan 2024 12:32:35 +0600 Subject: [PATCH 049/200] [FSSDK-9897] update return type of getFeatureVariable method (#887) --- lib/core/project_config/index.ts | 5 +++-- lib/export_types.ts | 1 + lib/optimizely/index.ts | 7 ++++--- lib/shared_types.ts | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/core/project_config/index.ts b/lib/core/project_config/index.ts index a58c9cdc2..07b206df6 100644 --- a/lib/core/project_config/index.ts +++ b/lib/core/project_config/index.ts @@ -32,6 +32,7 @@ import { VariableType, VariationVariable, Integration, + FeatureVariableValue, } from '../../shared_types'; interface TryCreatingProjectConfigConfig { @@ -692,8 +693,8 @@ export const getTypeCastValue = function( variableValue: string, variableType: VariableType, logger: LogHandler -): unknown { - let castValue; +): FeatureVariableValue { + let castValue : FeatureVariableValue; switch (variableType) { case FEATURE_VARIABLE_TYPES.BOOLEAN: diff --git a/lib/export_types.ts b/lib/export_types.ts index cfd498591..759bb86c0 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -22,6 +22,7 @@ export { UserAttributeValue, UserAttributes, OptimizelyConfig, + FeatureVariableValue, OptimizelyVariable, OptimizelyVariation, OptimizelyExperiment, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index c2b26478e..a7c6c29fe 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -35,6 +35,7 @@ import { FeatureVariable, OptimizelyOptions, OptimizelyDecideOption, + FeatureVariableValue, OptimizelyDecision, Client, } from '../shared_types'; @@ -777,7 +778,7 @@ export default class Optimizely implements Client { * type, or null if the feature key is invalid or * the variable key is invalid */ - getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): unknown { + getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): FeatureVariableValue { try { if (!this.isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable'); @@ -819,7 +820,7 @@ export default class Optimizely implements Client { variableType: string | null, userId: string, attributes?: UserAttributes - ): unknown { + ): FeatureVariableValue { if (!this.validateInputs({ feature_key: featureKey, variable_key: variableKey, user_id: userId }, attributes)) { return null; } @@ -911,7 +912,7 @@ export default class Optimizely implements Client { variation: Variation | null, variable: FeatureVariable, userId: string - ): unknown { + ): FeatureVariableValue { const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 5caf705f9..f156eaee4 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -311,6 +311,8 @@ export interface OptimizelyExperiment { }; } +export type FeatureVariableValue = number | string | boolean | object | null; + export interface OptimizelyVariable { id: string; key: string; @@ -330,7 +332,7 @@ export interface Client { getForcedVariation(experimentKey: string, userId: string): string | null; isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean; getEnabledFeatures(userId: string, attributes?: UserAttributes): string[]; - getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): unknown; + getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): FeatureVariableValue; getFeatureVariableBoolean( featureKey: string, variableKey: string, From f61dc5213de56a45b9edd07924d47939f5cf2529 Mon Sep 17 00:00:00 2001 From: Andy Leap <104936100+andrewleap-optimizely@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:35:54 -0500 Subject: [PATCH 050/200] [FSSDK-9607] add pixelUrl to OdpConfig (#888) --- lib/core/odp/odp_config.ts | 19 ++++++++++++++++++- lib/core/odp/odp_manager.ts | 6 +++--- lib/core/project_config/index.tests.js | 9 +++++++++ lib/core/project_config/index.ts | 5 +++++ .../project_config/project_config_schema.ts | 11 +++++++---- lib/index.browser.tests.js | 5 +++-- lib/optimizely/index.ts | 8 ++++---- .../odp/event_api_manager/index.browser.ts | 6 +++--- lib/shared_types.ts | 1 + lib/tests/test_data.js | 4 ++++ tests/odpEventApiManager.spec.ts | 6 ++++-- tests/odpEventManager.spec.ts | 16 ++++++++++------ tests/odpManager.browser.spec.ts | 12 +++++++----- tests/odpManager.spec.ts | 16 +++++++++++----- tests/odpSegmentManager.spec.ts | 7 ++++--- 15 files changed, 93 insertions(+), 38 deletions(-) diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index dcfb008e9..8593dbd2d 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -45,6 +45,20 @@ export class OdpConfig { return this._apiKey; } + /** + * Url for sending events via pixel. + * @private + */ + private _pixelUrl: string; + + /** + * Getter to retrieve the ODP pixel URL + * @public + */ + get pixelUrl(): string { + return this._pixelUrl; + } + /** * All ODP segments used in the current datafile (associated with apiHost/apiKey). * @private @@ -59,9 +73,10 @@ export class OdpConfig { return this._segmentsToCheck; } - constructor(apiKey?: string, apiHost?: string, segmentsToCheck?: string[]) { + constructor(apiKey?: string, apiHost?: string, pixelUrl?: string, segmentsToCheck?: string[]) { this._apiKey = apiKey ?? ''; this._apiHost = apiHost ?? ''; + this._pixelUrl = pixelUrl ?? ''; this._segmentsToCheck = segmentsToCheck ?? []; } @@ -76,6 +91,7 @@ export class OdpConfig { } else { if (config.apiKey) this._apiKey = config.apiKey; if (config.apiHost) this._apiHost = config.apiHost; + if (config.pixelUrl) this._pixelUrl = config.pixelUrl; if (config.segmentsToCheck) this._segmentsToCheck = config.segmentsToCheck; return true; @@ -98,6 +114,7 @@ export class OdpConfig { return ( this._apiHost === configToCompare._apiHost && this._apiKey === configToCompare._apiKey && + this._pixelUrl === configToCompare._pixelUrl && checkArrayEquality(this.segmentsToCheck, configToCompare._segmentsToCheck) ); } diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 84aa630e2..ca3fc7f77 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -40,7 +40,7 @@ export interface IOdpManager { eventManager: IOdpEventManager | undefined; - updateSettings({ apiKey, apiHost, segmentsToCheck }: OdpConfig): boolean; + updateSettings({ apiKey, apiHost, pixelUrl, segmentsToCheck }: OdpConfig): boolean; close(): void; @@ -97,7 +97,7 @@ export abstract class OdpManager implements IOdpManager { /** * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments */ - updateSettings({ apiKey, apiHost, segmentsToCheck }: OdpConfig): boolean { + updateSettings({ apiKey, apiHost, pixelUrl, segmentsToCheck }: OdpConfig): boolean { if (!this.enabled) { return false; } @@ -114,7 +114,7 @@ export abstract class OdpManager implements IOdpManager { this.eventManager.flush(); - const newConfig = new OdpConfig(apiKey, apiHost, segmentsToCheck); + const newConfig = new OdpConfig(apiKey, apiHost, pixelUrl, segmentsToCheck); const configDidUpdate = this.odpConfig.update(newConfig); if (configDidUpdate) { diff --git a/lib/core/project_config/index.tests.js b/lib/core/project_config/index.tests.js index 6210f03f6..0500e8884 100644 --- a/lib/core/project_config/index.tests.js +++ b/lib/core/project_config/index.tests.js @@ -821,6 +821,10 @@ describe('lib/core/project_config', function() { assert.exists(config.hostForOdp); }); + it('should populate the pixelUrl value from the odp integration', () => { + assert.exists(config.pixelUrlForOdp); + }); + it('should contain all expected unique odp segments in allSegments', () => { assert.equal(config.allSegments.length, 3); assert.deepEqual(config.allSegments, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']); @@ -848,6 +852,11 @@ describe('lib/core/project_config', function() { assert.equal(config.hostForOdp, 'https://api.zaius.com/'); }); + it('should populate the pixelUrl value from the odp integration', () => { + assert.exists(config.pixelUrlForOdp); + assert.equal(config.pixelUrlForOdp, 'https://jumbe.zaius.com/'); + }); + it('should contain all expected unique odp segments in all segments', () => { assert.equal(config.allSegments.length, 0); }); diff --git a/lib/core/project_config/index.ts b/lib/core/project_config/index.ts index 07b206df6..b94e3373c 100644 --- a/lib/core/project_config/index.ts +++ b/lib/core/project_config/index.ts @@ -92,6 +92,7 @@ export interface ProjectConfig { integrationKeyMap?: { [key: string]: Integration }; publicKeyForOdp?: string; hostForOdp?: string; + pixelUrlForOdp?: string; allSegments: string[]; } @@ -203,6 +204,10 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str if (integration.host && !projectConfig.hostForOdp) { projectConfig.hostForOdp = integration.host; } + + if (integration.pixelUrl && !projectConfig.pixelUrlForOdp) { + projectConfig.pixelUrlForOdp = integration.pixelUrl; + } } }); } diff --git a/lib/core/project_config/project_config_schema.ts b/lib/core/project_config/project_config_schema.ts index 27fbb0aad..70cecb3d4 100644 --- a/lib/core/project_config/project_config_schema.ts +++ b/lib/core/project_config/project_config_schema.ts @@ -290,10 +290,13 @@ var schemaDefinition = { }, publicKey: { type: 'string' - } - } - } - } + }, + pixelUrl: { + type: 'string' + }, + }, + }, + }, }, }; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 013bb016b..ecad0810e 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -879,7 +879,7 @@ describe('javascript-sdk (Browser)', function() { client.sendOdpEvent('test', '', new Map([['eamil', 'test@test.test']]), new Map([['key', 'value']])); clock.tick(10000); - + const eventRequestUrl = new URL(fakeRequestHandler.makeRequest.lastCall.args[0]); const searchParams = eventRequestUrl.searchParams; @@ -1116,8 +1116,9 @@ describe('javascript-sdk (Browser)', function() { clock.tick(100); let publicKey = datafile.integrations[0].publicKey; + let pixelUrl = datafile.integrations[0].pixelUrl; - const pixelApiEndpoint = 'https://jumbe.zaius.com/v2/zaius.gif'; + const pixelApiEndpoint = `${pixelUrl}/v2/zaius.gif`; let requestEndpoint = new URL(requestParams.get('endpoint')); assert.equal(requestEndpoint.origin + requestEndpoint.pathname, pixelApiEndpoint); assert.equal(requestParams.get('method'), 'GET'); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index a7c6c29fe..07b106003 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -148,7 +148,7 @@ export default class Optimizely implements Client { NotificationRegistry.getNotificationCenter(config.sdkKey)?.sendNotifications( NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE ); - + this.updateOdpSettings(); }); @@ -186,7 +186,7 @@ export default class Optimizely implements Client { if (config.odpManager?.initPromise) { dependentPromises.push(config.odpManager.initPromise); } - + this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { // If no odpManager exists yet, creates a new one if (config.odpManager != null) { @@ -1678,13 +1678,13 @@ export default class Optimizely implements Client { } /** - * Updates ODP Config with most recent ODP key, host, and segments from the project config + * Updates ODP Config with most recent ODP key, host, pixelUrl, and segments from the project config */ private updateOdpSettings(): void { const projectConfig = this.projectConfigManager.getConfig(); if (this.odpManager != null && projectConfig != null) { this.odpManager.updateSettings( - new OdpConfig(projectConfig.publicKeyForOdp, projectConfig.hostForOdp, projectConfig.allSegments) + new OdpConfig(projectConfig.publicKeyForOdp, projectConfig.hostForOdp, projectConfig.pixelUrlForOdp, projectConfig.allSegments) ); } } diff --git a/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/plugins/odp/event_api_manager/index.browser.ts index 7c2baef70..592978f63 100644 --- a/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/lib/plugins/odp/event_api_manager/index.browser.ts @@ -20,8 +20,8 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { if (!this.odpConfig?.isReady()) { throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); } - const apiHost = this.odpConfig.apiHost; - const pixelApiEndpoint = new URL(pixelApiPath, apiHost.replace('api', 'jumbe')).href; + const pixelUrl = this.odpConfig.pixelUrl; + const pixelApiEndpoint = new URL(pixelApiPath, pixelUrl).href; return pixelApiEndpoint; } @@ -33,7 +33,7 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); } - + // this cannot be cached cause OdpConfig is mutable // and can be updated in place and it is done so in odp // manager. We should make OdpConfig immutable and diff --git a/lib/shared_types.ts b/lib/shared_types.ts index f156eaee4..2ae1ffb99 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -221,6 +221,7 @@ export interface Integration { key: string; host?: string; publicKey?: string; + pixelUrl?: string; } export interface TrafficAllocation { diff --git a/lib/tests/test_data.js b/lib/tests/test_data.js index e2d196967..eddf1a3fd 100644 --- a/lib/tests/test_data.js +++ b/lib/tests/test_data.js @@ -3225,11 +3225,13 @@ var odpIntegratedConfigWithSegments = { key: 'odp', host: 'https://api.zaius.com/', publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + pixelUrl: 'https://jumbe.zaius.com/', }, { key: 'odp', host: 'https://api.zzzzaius.com/', publicKey: 'W4WzcEs-ABgXorzssssY7h1LCQ', + pixelUrl: 'https://jumbe.zzzzaius.com/', }, { key: 'odp', @@ -3359,6 +3361,7 @@ var odpIntegratedConfigWithoutSegments = { key: 'odp', host: 'https://api.zaius.com/', publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + pixelUrl: 'https://jumbe.zaius.com/', }, { key: 'odp', @@ -3394,6 +3397,7 @@ var odpIntegratedConfigWithoutKey = { { host: 'https://api.zaius.com/', publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + pixelUrl: 'https://jumbe.zaius.com/', }, ], revision: '100', diff --git a/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts index 0ed4d4dc7..bfe813af6 100644 --- a/tests/odpEventApiManager.spec.ts +++ b/tests/odpEventApiManager.spec.ts @@ -37,8 +37,9 @@ const ODP_EVENTS = [ const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; -const odpConfig = new OdpConfig(API_KEY, API_HOST, []); +const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); describe('NodeOdpEventApiManager', () => { let mockLogger: LogHandler; @@ -133,9 +134,10 @@ describe('NodeOdpEventApiManager', () => { const updatedOdpConfig = new OdpConfig( 'updated-key', 'https://updatedhost.test/', + 'https://updatedpixel.test/', ['updated-seg'], ) - + manager.updateSettings(updatedOdpConfig); await manager.sendEvents(ODP_EVENTS); diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 56c98da46..59a3d7669 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -28,6 +28,7 @@ import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; const MOCK_IDEMPOTENCE_ID = 'c1dc758e-f095-4f09-9b49-172d74c53880'; const EVENTS: OdpEvent[] = [ new OdpEvent( @@ -145,7 +146,7 @@ describe('OdpEventManager', () => { beforeAll(() => { mockLogger = mock<LogHandler>(); mockApiManager = mock<IOdpEventApiManager>(); - odpConfig = new OdpConfig(API_KEY, API_HOST, []); + odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); logger = instance(mockLogger); apiManager = instance(mockApiManager); }); @@ -159,7 +160,7 @@ describe('OdpEventManager', () => { when(mockApiManager.sendEvents(anything())).thenResolve(false); when(mockApiManager.updateSettings(anything())).thenReturn(undefined); - const apiManager = instance(mockApiManager); + const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -172,12 +173,12 @@ describe('OdpEventManager', () => { const [passedConfig] = capture(mockApiManager.updateSettings).last(); expect(passedConfig).toEqual(odpConfig); }); - + it('should update api manager setting with updatetd odp config on updateSettings', () => { when(mockApiManager.sendEvents(anything())).thenResolve(false); when(mockApiManager.updateSettings(anything())).thenReturn(undefined); - const apiManager = instance(mockApiManager); + const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -190,9 +191,10 @@ describe('OdpEventManager', () => { const updatedOdpConfig = new OdpConfig( 'updated-key', 'https://updatedhost.test/', + 'https://pixel.test/', ['updated-seg'], ) - + eventManager.updateSettings(updatedOdpConfig); verify(mockApiManager.updateSettings(anything())).twice(); @@ -541,13 +543,15 @@ describe('OdpEventManager', () => { }); const apiKey = 'testing-api-key'; const apiHost = 'https://some.other.example.com/'; + const pixelUrl = 'https://some.other.pixel.com/'; const segmentsToCheck = ['empty-cart', '1-item-cart']; - const differentOdpConfig = new OdpConfig(apiKey, apiHost, segmentsToCheck); + const differentOdpConfig = new OdpConfig(apiKey, apiHost, pixelUrl, segmentsToCheck); eventManager.updateSettings(differentOdpConfig); expect(eventManager['odpConfig'].apiKey).toEqual(apiKey); expect(eventManager['odpConfig'].apiHost).toEqual(apiHost); + expect(eventManager['odpConfig'].pixelUrl).toEqual(pixelUrl); expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[0]); expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[1]); }); diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index 385616593..ade9d48ce 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -38,17 +38,19 @@ import { OdpEvent } from '../lib/core/odp/odp_event'; const keyA = 'key-a'; const hostA = 'host-a'; +const pixelA = 'pixel-a'; const segmentsA = ['a']; const userA = 'fs-user-a'; const vuidA = 'vuid_a'; -const odpConfigA = new OdpConfig(keyA, hostA, segmentsA); +const odpConfigA = new OdpConfig(keyA, hostA, pixelA, segmentsA); const keyB = 'key-b'; const hostB = 'host-b'; +const pixelB = 'pixel-b'; const segmentsB = ['b']; const userB = 'fs-user-b'; const vuidB = 'vuid_b'; -const odpConfigB = new OdpConfig(keyB, hostB, segmentsB); +const odpConfigB = new OdpConfig(keyB, hostB, pixelB, segmentsB); describe('OdpManager', () => { let odpConfig: OdpConfig; @@ -122,7 +124,7 @@ describe('OdpManager', () => { verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); - browserOdpManager.updateSettings(new OdpConfig('valid', 'host', [])); + browserOdpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); expect(browserOdpManager.odpConfig).toBeUndefined; await browserOdpManager.fetchQualifiedSegments('vuid_user1', []); @@ -201,7 +203,7 @@ describe('OdpManager', () => { }, }); - const didUpdateA = browserOdpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); + const didUpdateA = browserOdpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); expect(didUpdateA).toBe(true); browserOdpManager.fetchQualifiedSegments(vuidA); @@ -213,7 +215,7 @@ describe('OdpManager', () => { const fetchQualifiedSegmentsArgsA = capture(mockSegmentApiManager.fetchSegments).last(); expect(fetchQualifiedSegmentsArgsA).toStrictEqual([keyA, hostA, ODP_USER_KEY.VUID, vuidA, segmentsA]); - const didUpdateB = browserOdpManager.updateSettings(new OdpConfig(keyB, hostB, segmentsB)); + const didUpdateB = browserOdpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); expect(didUpdateB).toBe(true); browserOdpManager.fetchQualifiedSegments(vuidB); diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 87009135e..5d3b0e465 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -34,11 +34,13 @@ import { ServerLRUCache } from '../lib/utils/lru_cache'; const keyA = 'key-a'; const hostA = 'host-a'; +const pixelA = 'pixel-a'; const segmentsA = ['a']; const userA = 'fs-user-a'; const keyB = 'key-b'; const hostB = 'host-b'; +const pixelB = 'pixel-b'; const segmentsB = ['b']; const userB = 'fs-user-b'; @@ -108,7 +110,7 @@ describe('OdpManager', () => { }); verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); - odpManager.updateSettings(new OdpConfig('valid', 'host', [])); + odpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); expect(odpManager.odpConfig).toBeUndefined; await odpManager.fetchQualifiedSegments('user1', []); @@ -152,18 +154,20 @@ describe('OdpManager', () => { }, }); - odpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); + odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); expect(odpManager.odpConfig.apiKey).toBe(keyA); expect(odpManager.odpConfig.apiHost).toBe(hostA); + expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); // odpManager.identifyUser(userA); // verify(mockEventApiManager.sendEvents(keyA, hostA, anything())).once(); - odpManager.updateSettings(new OdpConfig(keyB, hostB, segmentsB)); + odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); expect(odpManager.odpConfig.apiKey).toBe(keyB); expect(odpManager.odpConfig.apiHost).toBe(hostB); + expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); // odpManager.identifyUser(userB); @@ -179,17 +183,19 @@ describe('OdpManager', () => { }, }); - odpManager.updateSettings(new OdpConfig(keyA, hostA, segmentsA)); + odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); expect(odpManager.odpConfig.apiKey).toBe(keyA); expect(odpManager.odpConfig.apiHost).toBe(hostA); + expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); await odpManager.fetchQualifiedSegments(userA); verify(mockSegmentApiManager.fetchSegments(keyA, hostA, ODP_USER_KEY.FS_USER_ID, userA, anything())).once(); - odpManager.updateSettings(new OdpConfig(keyB, hostB, segmentsB)); + odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); expect(odpManager.odpConfig.apiKey).toBe(keyB); expect(odpManager.odpConfig.apiHost).toBe(hostB); + expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); await odpManager.fetchQualifiedSegments(userB); verify(mockSegmentApiManager.fetchSegments(keyB, hostB, ODP_USER_KEY.FS_USER_ID, userB, anything())).once(); diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts index d6e571dec..5deea4348 100644 --- a/tests/odpSegmentManager.spec.ts +++ b/tests/odpSegmentManager.spec.ts @@ -54,8 +54,8 @@ describe('OdpSegmentManager', () => { const userKey: ODP_USER_KEY = ODP_USER_KEY.VUID; const userValue = 'test-user'; - const validTestOdpConfig = new OdpConfig('valid-key', 'host', ['new-customer']); - const invalidTestOdpConfig = new OdpConfig('invalid-key', 'host', ['new-customer']); + const validTestOdpConfig = new OdpConfig('valid-key', 'host', 'pixel-url', ['new-customer']); + const invalidTestOdpConfig = new OdpConfig('invalid-key', 'host', 'pixel-url', ['new-customer']); beforeEach(() => { resetCalls(mockLogHandler); @@ -63,7 +63,8 @@ describe('OdpSegmentManager', () => { const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; - odpConfig = new OdpConfig(API_KEY, API_HOST, []); + const PIXEL_URL = 'https://odp.pixel.com/'; + odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); const segmentsCache = new LRUCache<string, string[]>({ maxSize: 1000, timeout: 1000, From af1ebd2e6e43ecbb0d3211a561cae56557ff624e Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 19 Jan 2024 03:16:32 +0600 Subject: [PATCH 051/200] [FSSDK-8983] Specify package entry point using exports field (#836) --- package.json | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/package.json b/package.json index 6a135e7d3..6a2110b5b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,54 @@ "browser": "dist/optimizely.browser.min.js", "react-native": "dist/optimizely.react_native.min.js", "typings": "dist/index.browser.d.ts", + "exports": { + ".": { + "node": { + "types": "./dist/index.node.d.ts", + "default": "./dist/optimizely.node.min.js" + }, + "react-native": { + "types": "./dist/index.react_native.d.ts", + "default": "./dist/optimizely.react_native.min.js" + }, + "default": { + "types": "./dist/index.browser.d.ts", + "require": "./dist/optimizely.browser.min.js", + "import": "./dist/optimizely.browser.es.js", + "default": "./dist/optimizely.browser.es.min.js" + } + }, + "./lite": { + "types": "./dist/index.lite.d.ts", + "node": "./dist/optimizely.lite.min.js", + "import": "./dist/optimizely.lite.es.js", + "default": "./dist/optimizely.lite.min.js" + }, + "./dist/optimizely.lite.es": { + "types": "./dist/index.lite.d.ts", + "default": "./dist/optimizely.lite.es.js" + }, + "./dist/optimizely.lite.es.js": { + "types": "./dist/index.lite.d.ts", + "default": "./dist/optimizely.lite.es.js" + }, + "./dist/optimizely.lite.es.min": { + "types": "./dist/index.lite.d.ts", + "default": "./dist/optimizely.lite.es.min.js" + }, + "./dist/optimizely.lite.es.min.js": { + "types": "./dist/index.lite.d.ts", + "default": "./dist/optimizely.lite.es.min.js" + }, + "./dist/optimizely.lite.min": { + "types": "./dist/index.lite.d.ts", + "default": "./dist/optimizely.lite.min.js" + }, + "./dist/optimizely.lite.min.js": { + "types": "./dist/index.lite.d.ts", + "default": "./dist/optimizely.lite.min.js" + } + }, "scripts": { "clean": "rm -rf dist", "clean:win": "(if exist dist rd /s/q dist)", From d4d87e71da6ed80fe38950a717476309ea70fd9c Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 19 Jan 2024 17:54:44 +0600 Subject: [PATCH 052/200] [FSSDK-9912] update uuid, @types/uuid and ua-parser-js dependencies (#892) --- package-lock.json | 26 +++++++++++++++++--------- package.json | 6 +++--- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1576246c5..c157341c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1282,9 +1282,9 @@ "dev": true }, "@types/uuid": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.11.tgz", - "integrity": "sha512-CJNkbEu4IdVuBMRVaNC2GjASgJK7ziqDlVXWuJ1pvhOLADl7nzxhTKjHRdOmo2SuXuygcWBmzgYgn9foTX0UiA==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", "dev": true }, "@types/yargs": { @@ -3792,6 +3792,14 @@ "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "istanbul-lib-report": { @@ -6777,9 +6785,9 @@ "dev": true }, "ua-parser-js": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.36.tgz", - "integrity": "sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==" + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==" }, "universalify": { "version": "0.2.0", @@ -6829,9 +6837,9 @@ "dev": true }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" }, "v8-to-istanbul": { "version": "9.1.0", diff --git a/package.json b/package.json index 6a2110b5b..c03746e2d 100644 --- a/package.json +++ b/package.json @@ -93,8 +93,8 @@ "decompress-response": "^4.2.1", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", - "ua-parser-js": "^1.0.35", - "uuid": "^8.3.2" + "ua-parser-js": "^1.0.37", + "uuid": "^9.0.1" }, "devDependencies": { "@react-native-async-storage/async-storage": "^1.2.0", @@ -107,7 +107,7 @@ "@types/nise": "^1.4.0", "@types/node": "^18.7.18", "@types/ua-parser-js": "^0.7.36", - "@types/uuid": "^3.4.4", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", "bluebird": "^3.4.6", From 961f6a124f132bc49bee0b318acdca2f7f39ad2b Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 19 Jan 2024 20:18:05 +0600 Subject: [PATCH 053/200] [FSSDK-8580] prepare for release 5.0.0 (#893) --- CHANGELOG.md | 57 +++++++++++++++++++ LICENSE | 2 +- README.md | 94 +++++++++----------------------- lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 4 +- package-lock.json | 2 +- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 10 files changed, 92 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 990a47eb7..2241654e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.0.0] - January 19, 2024 + +### New Features + +The 5.0.0 release introduces a new primary feature, [Advanced Audience Targeting]( https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) enabled through integration with [Optimizely Data Platform (ODP)](https://docs.developers.optimizely.com/optimizely-data-platform/docs) ([#765](https://github.com/optimizely/javascript-sdk/pull/765), [#775](https://github.com/optimizely/javascript-sdk/pull/775), [#776](https://github.com/optimizely/javascript-sdk/pull/776), [#777](https://github.com/optimizely/javascript-sdk/pull/777), [#778](https://github.com/optimizely/javascript-sdk/pull/778), [#786](https://github.com/optimizely/javascript-sdk/pull/786), [#789](https://github.com/optimizely/javascript-sdk/pull/789), [#790](https://github.com/optimizely/javascript-sdk/pull/790), [#797](https://github.com/optimizely/javascript-sdk/pull/797), [#799](https://github.com/optimizely/javascript-sdk/pull/799), [#808](https://github.com/optimizely/javascript-sdk/pull/808)). + +You can use ODP, a high-performance [Customer Data Platform (CDP)]( https://www.optimizely.com/optimization-glossary/customer-data-platform/), to easily create complex real-time segments (RTS) using first-party and 50+ third-party data sources out of the box. You can create custom schemas that support the user attributes important for your business, and stitch together user behavior done on different devices to better understand and target your customers for personalized user experiences. ODP can be used as a single source of truth for these segments in any Optimizely or 3rd party tool. + +With ODP accounts integrated into Optimizely projects, you can build audiences using segments pre-defined in ODP. The SDK will fetch the segments for given users and make decisions using the segments. For access to ODP audience targeting in your Feature Experimentation account, please contact your Customer Success Manager. + +This version includes the following changes: + +- New API added to `OptimizelyUserContext`: + + - `fetchQualifiedSegments()`: this API will retrieve user segments from the ODP server. The fetched segments will be used for audience evaluation. The fetched data will be stored in the local cache to avoid repeated network delays. + + - When an `OptimizelyUserContext` is created, the SDK will automatically send an identify request to the ODP server to facilitate observing user activities. + +- New APIs added to `OptimizelyClient`: + + - `sendOdpEvent()`: customers can build/send arbitrary ODP events that will bind user identifiers and data to user profiles in ODP. + + - `createUserContext()` with anonymous user IDs: user-contexts can be created without a userId. The SDK will create and use a persistent `VUID` specific to a device when userId is not provided. + +For details, refer to our documentation pages: + +- [Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) + +- [Client SDK Support](https://docs.developers.optimizely.com/feature-experimentation/v1.0/docs/advanced-audience-targeting-for-client-side-sdks) + +- [Initialize JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/initialize-sdk-javascript-aat) + +- [OptimizelyUserContext JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizelyusercontext-javascript-aat) + +- [Advanced Audience Targeting segment qualification methods](https://docs.developers.optimizely.com/feature-experimentation/docs/advanced-audience-targeting-segment-qualification-methods-javascript) + +- [Send Optimizely Data Platform data using Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/send-odp-data-using-advanced-audience-targeting-javascript) + +Additionally, a handful of major package updates are also included in this release including `murmurhash`, `uuid`, and others. For more information, check out the **Breaking Changes** section below. ([#892](https://github.com/optimizely/javascript-sdk/pull/892), [#762](https://github.com/optimizely/javascript-sdk/pull/762)) + +### Breaking Changes +- `ODPManager` in the SDK is enabled by default. Unless an ODP account is integrated into the Optimizely projects, most `ODPManager` functions will be ignored. If needed, `ODPManager` can be disabled when `OptimizelyClient` is instantiated. +- Updated `murmurhash` dependency to version `2.0.1`. +- Updated `uuid` dependency to version `9.0.1`. +- Dropped support for the following browser versions. + - All versions of Microsof Internet Explorer. + - Chrome versions earlier than `102.0`. + - Microsoft Edge versions earlier than `84.0`. + - Firefox versions earlier than `91.0`. + - Opera versions earlier than `76.0`. + - Safari versions earlier than `13.0`. +- Dropped support for Node JS versions earlier than `16`. + +## Changed +- Updated `createUserContext`'s `userId` parameter to be optional due to the Browser variation's use of the new `vuid` field. Note: The Node variation of the SDK does **not** use the new `vuid` field and you should pass in a `userId` when within the context of the Node variant. + + ## [4.10.0] - October 11, 2023 ### New Features diff --git a/LICENSE b/LICENSE index b9f80c5bd..e2d144779 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2016-2017, Optimizely, Inc. and contributors + © Optimizely 2016 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 5e65ad0db..86d82035c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). -Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/welcome). +Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/introduction). Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feature-flagging/) for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap. @@ -16,13 +16,9 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat ## Get Started -> For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. +> For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. -> For **React** applications, refer to the [React SDK developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-sdk). - -> For **React Native** applications, refer to the [JavaScript (React Native) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-native-sdk). - -> For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk). +> For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-node-sdk). > For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: > - [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) @@ -31,13 +27,13 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat > - [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) > - [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) > -> Note: These starter kits use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. +> Note: We recommend using the **Lite** version of the sdk for edge platforms. These starter kits also use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. ### Prerequisites -Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets any ES5-compliant JavaScript environment. We officially support: -- Node.js >= 8.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. -- [Modern Web Browsers, such as IE 10+, Firefox 21+, Safari 6+, and Chrome 23+](https://caniuse.com/#feat=es5) +Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets modern ES5-compliant JavaScript environment. We officially support: +- Node.js >= 16.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. +- Modern Web Browsers, such as Microsoft Edge 84+, Firefox 91+, Safari 13+, and Chrome 102+, Opera 76+ In addition, other environments are likely compatible but are not formally supported including: - Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. @@ -48,44 +44,8 @@ In addition, other environments are likely compatible but are not formally suppo * JavaScript (Browser): Modern web browser that is ES5-compliant. -* JavaScript (Node): Node 8.0+ - -* The following peer dependencies may be required for use in production: - -```json -{ - "json-schema@0.4.0": { - "licenses": [ - "AFLv2.1", - "BSD" - ], - "publisher": "Kris Zyp", - "repository": "https://github.com/kriszyp/json-schema" - }, - "murmurhash@2.0.1": { - "licenses": "MIT*", - "repository": "https://github.com/perezd/node-murmurhash" - }, - "uuid@8.3.2": { - "licenses": "MIT", - "repository": "https://github.com/kelektiv/node-uuid" - }, - "decompress-response@4.2.1": { - "licenses": "MIT", - "repository": "https://github.com/sindresorhus/decompress-response" - } -} -``` - -To regenerate this, run the following command: - -```sh -npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)' -``` - -and remove the self (`@optimizely/optimizely-sdk`) entry. +* JavaScript (Node): Node 16.0.0+ -> Note: The `jq` command line tool is required to run the above script. You may install `jq` to your environment by using Homebrew on MacOS using the command `brew install jq`. For Windows users, please visit the [JQ GitHub repository](https://github.com/stedolan/jq) to learn more. ### Install the SDK @@ -116,7 +76,7 @@ See the [Optimizely Feature Experimentation developer documentation for JavaScri ### Initialization (Browser) -The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): +The package has different entry points for different environments. The browser entry point is an ES module, which can be used with an appropriate bundler like **Webpack** or **Rollup**. Additionally, for ease of use during initial evaluations you can include a standalone umd bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): ```html <script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.min.js"></script> @@ -134,11 +94,13 @@ const optimizelyClient = window.optimizelySdk.createInstance({ sdkKey: '<YOUR_SDK_KEY>', // datafile: window.optimizelyDatafile, // etc. -}) +}); -optimizelyClient.onReady().then(() => { - // Create the Optimizely user context, make decisions, and more here! -}) +optimizelyClient.onReady().then(({ success, reason }) => { + if (success) { + // Create the Optimizely user context, make decisions, and more here! + } +}); ``` Regarding `EventDispatcher`s: In Node.js and browser environments, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) modules and by [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Browser_compatibility), respectively. In all other environments, you must supply your own `EventDispatcher`. @@ -149,20 +111,22 @@ See the [Optimizely Feature Experimentation developer documentation for JavaScri ### Initialization (Node) -The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): +The package has different entry points for different environments. The node entry point is CommonJS module. -```typescript -import optimizelyClient from "@optimizely/optimizely-sdk"; +```javascript +const optimizelySdk = require('@optimizely/optimizely-sdk'); -optimizelyClient.createInstance({ +const optimizelyClient = optimizelySdk.createInstance({ sdkKey: '<YOUR_SDK_KEY>', // datafile: window.optimizelyDatafile, // etc. }); -optimizelyClient.onReady().then(() => { - // Create the Optimizely user context, make decisions, and more here! -}) +optimizelyClient.onReady().then(({ success, reason }) => { + if (success) { + // Create the Optimizely user context, make decisions, and more here! + } +}); ``` Regarding `EventDispatcher`s: In Node.js environment, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) module. @@ -207,15 +171,9 @@ For more information regarding contributing to the Optimizely JavaScript SDK, pl ## Special Notes -### Migrating from 1.x.x - -This version represents a major version change and, as such, introduces some breaking changes: - -- The Node.js SDK is now combined with the JavaScript SDK. We now have just one package, `@optimizely/optimizely-sdk`, that works in many JavaScript environments. - -- We no longer support Node.js < 4.0.0, which collectively [reached end-of-life](https://github.com/nodejs/Release#end-of-life-releases) on 2016-12-31. +### Migrating from 4.x.x -- You will no longer be able to pass in `revenue` value as a stand-alone argument to the `track` call. Instead you will need to pass it as an entry in the [`eventTags`](https://developers.optimizely.com/x/solutions/sdks/reference/index.html?language=javascript#event-tags). +This version represents a major version change and, as such, introduces some breaking changes. Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for more details. ### Feature Management access diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index ecad0810e..0d5d3c1c3 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -192,7 +192,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta5'); + assert.equal(optlyInstance.clientVersion, '5.0.0'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 8db1cf76b..da60bf549 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta5'); + assert.equal(optlyInstance.clientVersion, '5.0.0'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 6bafd0ddc..3d2fafddc 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0-beta5'); + assert.equal(optlyInstance.clientVersion, '5.0.0'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 30ba4d090..ee05148f4 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -223,8 +223,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '5.0.0-beta5'; -export const NODE_CLIENT_VERSION = '5.0.0-beta5'; +export const BROWSER_CLIENT_VERSION = '5.0.0'; +export const NODE_CLIENT_VERSION = '5.0.0'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index c157341c7..8721617a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta5", + "version": "5.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c03746e2d..76bb1e215 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0-beta5", + "version": "5.0.0", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index c747c5ac9..fbdadfac2 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.0.0-beta5'); + expect(optlyInstance.clientVersion).toEqual('5.0.0'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 1cc2e1e1d6e1926ddfdb6425167514d3861a5888 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 8 Feb 2024 19:49:56 +0600 Subject: [PATCH 054/200] [FSSDK-9965] fix critical dependbot alerts (#896) --- lib/optimizely/index.tests.js | 3 +- package-lock.json | 22087 ++++++++++++++++++++++++++++++-- package.json | 9 +- 3 files changed, 21106 insertions(+), 993 deletions(-) diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 8c226493b..f7eb2bac2 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -23,7 +23,6 @@ import Optimizely from './'; import OptimizelyUserContext from '../optimizely_user_context'; import { OptimizelyDecideOption } from '../shared_types'; import AudienceEvaluator from '../core/audience_evaluator'; -import bluebird from 'bluebird'; import * as bucketer from '../core/bucketer'; import * as projectConfigManager from '../core/project_config/project_config_manager'; import * as enums from '../utils/enums'; @@ -93,7 +92,7 @@ describe('lib/optimizely', function() { var stubErrorHandler = { handleError: function() {} }; var stubEventDispatcher = { dispatchEvent: function() { - return bluebird.resolve(null); + return Promise.resolve(null); }, }; var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); diff --git a/package-lock.json b/package-lock.json index 8721617a7..85fbaaa47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,105 +1,207 @@ { "name": "@optimizely/optimizely-sdk", "version": "5.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@aashutoshrathi/word-wrap": { + "packages": { + "": { + "name": "@optimizely/optimizely-sdk", + "version": "5.0.0", + "license": "Apache-2.0", + "dependencies": { + "decompress-response": "^4.2.1", + "json-schema": "^0.4.0", + "murmurhash": "^2.0.1", + "ua-parser-js": "^1.0.37", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@react-native-async-storage/async-storage": "^1.2.0", + "@react-native-community/netinfo": "^5.9.10", + "@rollup/plugin-commonjs": "^11.0.2", + "@rollup/plugin-node-resolve": "^7.1.1", + "@types/chai": "^4.2.11", + "@types/jest": "^23.3.14", + "@types/mocha": "^5.2.7", + "@types/nise": "^1.4.0", + "@types/node": "^18.7.18", + "@types/ua-parser-js": "^0.7.36", + "@types/uuid": "^9.0.7", + "@typescript-eslint/eslint-plugin": "^5.33.0", + "@typescript-eslint/parser": "^5.33.0", + "chai": "^4.2.0", + "coveralls": "^3.0.2", + "eslint": "^8.21.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-prettier": "^3.1.2", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.0.0", + "jest-localstorage-mock": "^2.4.22", + "json-loader": "^0.5.4", + "karma": "^6.4.0", + "karma-browserstack-launcher": "^1.5.1", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.1.1", + "karma-mocha": "^1.3.0", + "karma-webpack": "^5.0.1", + "lodash": "^4.17.11", + "mocha": "^10.2.0", + "mocha-lcov-reporter": "^1.3.0", + "nise": "^1.4.10", + "nock": "11.9.1", + "nyc": "^15.0.1", + "prettier": "^1.19.1", + "promise-polyfill": "8.1.0", + "rollup": "2.2.0", + "rollup-plugin-terser": "^5.3.0", + "rollup-plugin-typescript2": "^0.27.1", + "sinon": "^2.3.1", + "ts-jest": "^29.1.2", + "ts-loader": "^9.3.1", + "ts-mockito": "^2.6.1", + "ts-node": "^8.10.2", + "tslib": "^2.4.0", + "typescript": "^4.7.4", + "webpack": "^5.74.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/runtime": "^7.0.0", + "@react-native-async-storage/async-storage": "^1.2.0", + "@react-native-community/netinfo": "5.9.4" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + }, + "@react-native-community/netinfo": { + "optional": true + } + } + }, + "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "@ampproject/remapping": { + "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, - "requires": { + "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" } }, - "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, - "requires": { - "@babel/highlight": "^7.22.13", + "dependencies": { + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "@babel/core": { + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", "dev": true, - "requires": { + "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", "@babel/generator": "^7.22.15", @@ -116,353 +218,16964 @@ "json5": "^2.2.3", "semver": "^6.3.1" }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "requires": { - "@babel/types": "^7.22.15", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-compilation-targets": { + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", "dev": true, - "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "peer": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } + "yallist": "^3.0.2" } }, - "@babel/helper-environment-visitor": { + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", + "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, - "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-hoist-variables": { + "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, - "requires": { + "dependencies": { "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-module-imports": { + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, - "requires": { + "dependencies": { "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-module-transforms": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz", - "integrity": "sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@babel/helper-plugin-utils": { + "node_modules/@babel/helper-optimise-call-expression": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } }, - "@babel/helper-simple-access": { + "node_modules/@babel/helper-plugin-utils": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, - "requires": { - "@babel/types": "^7.22.5" + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-split-export-declaration": { + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, - "requires": { + "dependencies": { "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "@babel/helper-validator-identifier": { + "node_modules/@babel/helper-validator-identifier": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", - "dev": true + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "@babel/helpers": { + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", "dev": true, - "requires": { + "dependencies": { "@babel/template": "^7.22.15", "@babel/traverse": "^7.22.15", "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "@babel/plugin-syntax-async-generators": { + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.23.3.tgz", + "integrity": "sha512-Q23MpLZfSGZL1kU7fWqV262q65svLSCIP5kZ/JCW/rKTCm/FrLjpvEd2kfUYMVeHh4QhV/xzyoRAHWrAZJrE3Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-default-from": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-bigint": { + "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-class-properties": { + "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-json-strings": { + "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, - "requires": { + "peer": true, + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-logical-assignment-operators": { + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.23.3.tgz", + "integrity": "sha512-KeENO5ck1IeZ/l2lFZNy+mpobV3D2Zy5C1YFnWm+YuY5mQiAWc4yAp13dqgguwsBsFVLh4LPCEqCa5qW13N+hw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", + "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-nullish-coalescing-operator": { + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-numeric-separator": { + "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", + "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", + "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", + "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", + "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz", + "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", + "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", + "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.8", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.23.3.tgz", + "integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-flow-strip-types": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", + "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", + "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", + "dev": true, + "peer": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "peer": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "peer": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/register/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/register/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "peer": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true, + "peer": true + }, + "node_modules/@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "peer": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/@jest/core/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@jest/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/core/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@jest/core/node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/core/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/reporters/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz", + "integrity": "sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==", + "dev": true, + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, + "node_modules/@react-native-community/cli": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", + "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-clean": "12.3.2", + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-doctor": "12.3.2", + "@react-native-community/cli-hermes": "12.3.2", + "@react-native-community/cli-plugin-metro": "12.3.2", + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-types": "12.3.2", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", + "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", + "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", + "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", + "dev": true, + "peer": true, + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", + "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-hermes": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", + "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", + "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", + "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" + } + }, + "node_modules/@react-native-community/cli-plugin-metro": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", + "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==", + "dev": true, + "peer": true + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", + "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", + "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", + "dev": true, + "peer": true, + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", + "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", + "dev": true, + "peer": true, + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/@react-native-community/cli/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/netinfo": { + "version": "5.9.10", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", + "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", + "dev": true, + "peerDependencies": { + "react-native": ">=0.59" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", + "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", + "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native/codegen": "0.73.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.73.21", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", + "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.73.4", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.73.16", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.16.tgz", + "integrity": "sha512-eNH3v3qJJF6f0n/Dck90qfC9gVOR4coAXMTdYECO33GfgjTi+73vf/SBqlXw9HICH/RNZYGPM3wca4FRF7TYeQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native/dev-middleware": "0.73.7", + "@react-native/metro-babel-transformer": "0.73.15", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.3", + "metro-config": "^0.80.3", + "metro-core": "^0.80.3", + "node-fetch": "^2.2.0", + "readline": "^1.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", + "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.73.7", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz", + "integrity": "sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg==", + "dev": true, + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.73.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^1.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "open": "^7.0.3", + "serve-static": "^1.13.1", + "temp-dir": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", + "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.21", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "dev": true, + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", + "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.0.8", + "commondir": "^1.0.1", + "estree-walker": "^1.0.1", + "glob": "^7.1.2", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", + "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.14.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "peer": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "peer": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", + "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", + "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", + "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", + "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "dev": true + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.14", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", + "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.44.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", + "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", + "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "23.3.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", + "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", + "dev": true + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "node_modules/@types/nise": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.1.tgz", + "integrity": "sha512-LWDwHYO1C3YPpIQWXHeXAVih2nLsgN1Q5RamkYZRIZYfsz8HGNRji8vNhHs54LjcSgVx6AJC/6n/Q3Tn+fUb3g==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.17.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", + "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", + "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", + "dev": true + }, + "node_modules/@types/ua-parser-js": { + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "peer": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "dev": true, + "peer": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "dev": true, + "peer": true, + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-fragments/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-fragments/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "dev": true, + "peer": true + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "peer": true + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true, + "peer": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true, + "peer": true, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", + "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.5.0", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + } + }, + "node_modules/browserstack-local": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.4.tgz", + "integrity": "sha512-OueHCaQQutO+Fezg+ZTieRn+gdV+JocLjiAQ8nYecu08GhIt3ms79cDHfpoZmECAdoQ6OLdm7ODd+DtQzl4lrA==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + } + }, + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "peer": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001585", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", + "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "node_modules/chai": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", + "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "peer": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "peer": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "peer": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true, + "peer": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "peer": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "peer": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js-compat": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", + "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", + "dev": true, + "peer": true, + "dependencies": { + "browserslist": "^4.22.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "peer": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "peer": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "peer": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/coveralls": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/create-jest/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/create-jest/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/create-jest/node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/create-jest/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/create-jest/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", + "dev": true, + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "peer": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "dev": true, + "peer": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecated-react-native-prop-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", + "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native/normalize-colors": "^0.73.0", + "invariant": "^2.2.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.661", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.661.tgz", + "integrity": "sha512-AFg4wDHSOk5F+zA8aR+SVIOabu7m0e7BiJnigCvPXzIGy731XENw/lmNxTySpVFtkFEy+eyt4oHhh5FF3NjQNw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz", + "integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", + "dev": true, + "peer": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "peer": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dev": true, + "peer": true, + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "dev": true + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "dependencies": { + "get-stdin": "^6.0.0" + }, + "bin": { + "eslint-config-prettier-check": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=3.14.1" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-xml-parser": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", + "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "peer": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "dev": true, + "peer": true + }, + "node_modules/flow-parser": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", + "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", + "deprecated": "This package is unmaintained. Use @sinonjs/formatio instead", + "dev": true, + "dependencies": { + "samsam": "1.x" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", + "dev": true, + "dependencies": { + "null-check": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hermes-estree": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", + "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", + "dev": true, + "peer": true + }, + "node_modules/hermes-parser": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", + "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", + "dev": true, + "peer": true, + "dependencies": { + "hermes-estree": "0.15.0" + } + }, + "node_modules/hermes-profile-transformer": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", + "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "dev": true, + "peer": true, + "dependencies": { + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-profile-transformer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "dev": true, + "peer": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true, + "peer": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "peer": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", + "dev": true + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "peer": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/jest-cli/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/jest-cli/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/jest-cli/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/jest-cli/node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-cli/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli/node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-localstorage-mock": { + "version": "2.4.26", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz", + "integrity": "sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==", + "dev": true, + "engines": { + "node": ">=6.16.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-resolve/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/jest-runner/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-runner/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-runtime/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-snapshot/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.12.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", + "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", + "dev": true, + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/jsc-android": { + "version": "250231.0.0", + "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", + "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", + "dev": true, + "peer": true + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "dev": true, + "peer": true + }, + "node_modules/jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/karma": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", + "integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-browserstack-launcher": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", + "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", + "dev": true, + "dependencies": { + "browserstack": "~1.5.1", + "browserstack-local": "^1.3.7", + "q": "~1.5.0" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", + "dev": true, + "peerDependencies": { + "chai": "*", + "karma": ">=0.10.9" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "dependencies": { + "fs-access": "^1.0.0", + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-mocha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", + "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", + "dev": true, + "dependencies": { + "minimist": "1.2.0" + } + }, + "node_modules/karma-mocha/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", + "dev": true + }, + "node_modules/karma-webpack": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", + "integrity": "sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==", + "dev": true, + "dependencies": { + "glob": "^7.1.3", + "minimatch": "^9.0.3", + "webpack-merge": "^4.1.5" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/karma-webpack/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/karma-webpack/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/ua-parser-js": { + "version": "0.7.36", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.36.tgz", + "integrity": "sha512-CPPLoCts2p7D8VbybttE3P2ylv0OBZEAy7a12DsulIEcAiMtWJy+PBgMXgWDI80D5UwqE8oQPHYnk13tm38M2Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", + "dev": true, + "bin": { + "lcov-parse": "bin/cli.js" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "peer": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true, + "peer": true + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } + }, + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/logkitty/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logkitty/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "peer": true + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "peer": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "deprecated": "Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "dev": true, + "peer": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "dev": true, + "peer": true + }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dev": true, + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.5.tgz", + "integrity": "sha512-OE/CGbOgbi8BlTN1QqJgKOBaC27dS0JBQw473JcivrpgVnqIsluROA7AavEaTVUrB9wPUZvoNVDROn5uiM2jfw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.18.2", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.80.5", + "metro-cache": "0.80.5", + "metro-cache-key": "0.80.5", + "metro-config": "0.80.5", + "metro-core": "0.80.5", + "metro-file-map": "0.80.5", + "metro-resolver": "0.80.5", + "metro-runtime": "0.80.5", + "metro-source-map": "0.80.5", + "metro-symbolicate": "0.80.5", + "metro-transform-plugins": "0.80.5", + "metro-transform-worker": "0.80.5", + "mime-types": "^2.1.27", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "rimraf": "^3.0.2", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.5.tgz", + "integrity": "sha512-sxH6hcWCorhTbk4kaShCWsadzu99WBL4Nvq4m/sDTbp32//iGuxtAnUK+ZV+6IEygr2u9Z0/4XoZ8Sbcl71MpA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "hermes-parser": "0.18.2", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", + "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", + "dev": true, + "peer": true + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", + "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "dev": true, + "peer": true, + "dependencies": { + "hermes-estree": "0.18.2" + } + }, + "node_modules/metro-cache": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.5.tgz", + "integrity": "sha512-2u+dQ4PZwmC7eZo9uMBNhQQMig9f+w4QWBZwXCdVy/RYOHM0eObgGdMEOwODo73uxie82T9lWzxr3aZOZ+Nqtw==", + "dev": true, + "peer": true, + "dependencies": { + "metro-core": "0.80.5", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-cache-key": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.5.tgz", + "integrity": "sha512-fr3QLZUarsB3tRbVcmr34kCBsTHk0Sh9JXGvBY/w3b2lbre+Lq5gtgLyFElHPecGF7o4z1eK9r3ubxtScHWcbA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-config": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.5.tgz", + "integrity": "sha512-elqo/lwvF+VjZ1OPyvmW/9hSiGlmcqu+rQvDKw5F5WMX48ZC+ySTD1WcaD7e97pkgAlJHVYqZ98FCjRAYOAFRQ==", + "dev": true, + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "jest-validate": "^29.6.3", + "metro": "0.80.5", + "metro-cache": "0.80.5", + "metro-core": "0.80.5", + "metro-runtime": "0.80.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-core": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.5.tgz", + "integrity": "sha512-vkLuaBhnZxTVpaZO8ZJVEHzjaqSXpOdpAiztSZ+NDaYM6jEFgle3/XIbLW91jTSf2+T8Pj5yB1G7KuOX+BcVwg==", + "dev": true, + "peer": true, + "dependencies": { + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.80.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-file-map": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.5.tgz", + "integrity": "sha512-bKCvJ05drjq6QhQxnDUt3I8x7bTcHo3IIKVobEr14BK++nmxFGn/BmFLRzVBlghM6an3gqwpNEYxS5qNc+VKcg==", + "dev": true, + "peer": true, + "dependencies": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/metro-file-map/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro-file-map/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/metro-minify-terser": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.5.tgz", + "integrity": "sha512-S7oZLLcab6YXUT6jYFX/ZDMN7Fq6xBGGAG8liMFU1UljX6cTcEC2u+UIafYgCLrdVexp/+ClxrIetVPZ5LtL/g==", + "dev": true, + "peer": true, + "dependencies": { + "terser": "^5.15.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-minify-terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + }, + "node_modules/metro-minify-terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/metro-minify-terser/node_modules/terser": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/metro-resolver": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.5.tgz", + "integrity": "sha512-haJ/Hveio3zv/Fr4eXVdKzjUeHHDogYok7OpRqPSXGhTXisNXB+sLN7CpcUrCddFRUDLnVaqQOYwhYsFndgUwA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-runtime": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.5.tgz", + "integrity": "sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.5.tgz", + "integrity": "sha512-DwSF4l03mKPNqCtyQ6K23I43qzU1BViAXnuH81eYWdHglP+sDlPpY+/7rUahXEo6qXEHXfAJgVoo1sirbXbmsQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.80.5", + "nullthrows": "^1.1.1", + "ob1": "0.80.5", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.5.tgz", + "integrity": "sha512-IsM4mTYvmo9JvIqwEkCZ5+YeDVPST78Q17ZgljfLdHLSpIivOHp9oVoiwQ/YGbLx0xRHRIS/tKiXueWBnj3UWA==", + "dev": true, + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "metro-source-map": "0.80.5", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-symbolicate/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.5.tgz", + "integrity": "sha512-7IdlTqK/k5+qE3RvIU5QdCJUPk4tHWEqgVuYZu8exeW+s6qOJ66hGIJjXY/P7ccucqF+D4nsbAAW5unkoUdS6g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.5.tgz", + "integrity": "sha512-Q1oM7hfP+RBgAtzRFBDjPhArELUJF8iRCZ8OidqCpYzQJVGuJZ7InSnIf3hn1JyqiUQwv2f1LXBO78i2rAjzyA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "metro": "0.80.5", + "metro-babel-transformer": "0.80.5", + "metro-cache": "0.80.5", + "metro-cache-key": "0.80.5", + "metro-minify-terser": "0.80.5", + "metro-source-map": "0.80.5", + "metro-transform-plugins": "0.80.5", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "peer": true + }, + "node_modules/metro/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", + "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", + "dev": true, + "peer": true + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", + "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "dev": true, + "peer": true, + "dependencies": { + "hermes-estree": "0.18.2" + } + }, + "node_modules/metro/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha-lcov-reporter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", + "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/murmurhash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", + "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nise": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "dev": true, + "dependencies": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/nock": { + "version": "11.9.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", + "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.13", + "mkdirp": "^0.5.0", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "peer": true + }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dev": true, + "peer": true, + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "peer": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "peer": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, + "peer": true + }, + "node_modules/nwsapi": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "dev": true + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ob1": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", + "integrity": "sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dev": true, + "peer": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "peer": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "peer": true + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dev": true, + "peer": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/promise-polyfill": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", + "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "peer": true + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "peer": true, + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", + "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "dev": true, + "peer": true, + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/react-native": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.4.tgz", + "integrity": "sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native-community/cli": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", + "@react-native/assets-registry": "0.73.1", + "@react-native/codegen": "0.73.3", + "@react-native/community-cli-plugin": "0.73.16", + "@react-native/gradle-plugin": "0.73.4", + "@react-native/js-polyfills": "0.73.1", + "@react-native/normalize-colors": "0.73.2", + "@react-native/virtualized-lists": "0.73.4", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "base64-js": "^1.5.1", + "chalk": "^4.0.0", + "deprecated-react-native-prop-types": "^5.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "jsc-android": "^250231.0.0", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.80.3", + "metro-source-map": "^0.80.3", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "pretty-format": "^26.5.2", + "promise": "^8.3.0", + "react-devtools-core": "^4.27.7", + "react-refresh": "^0.14.0", + "react-shallow-renderer": "^16.15.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.24.0-canary-efb381bbf-20230505", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.2", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "18.2.0" + } + }, + "node_modules/react-native/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/react-native/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/react-native/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-native/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "node_modules/react-native/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, + "peer": true + }, + "node_modules/react-native/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, + "peer": true, + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "dev": true, + "peer": true + }, + "node_modules/recast": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "dev": true, + "peer": true, + "dependencies": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "peer": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "peer": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "peer": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "peer": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "peer": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", + "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", + "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.5.5", + "jest-worker": "^24.9.0", + "rollup-pluginutils": "^2.8.2", + "serialize-javascript": "^4.0.0", + "terser": "^4.6.2" + }, + "peerDependencies": { + "rollup": ">=0.66.0 <3" + } + }, + "node_modules/rollup-plugin-terser/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "dependencies": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rollup-plugin-terser/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/rollup-plugin-typescript2": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", + "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "8.1.0", + "resolve": "1.17.0", + "tslib": "2.0.1" + }, + "peerDependencies": { + "rollup": ">=1.26.3", + "typescript": ">=2.4.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/rollup/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", + "dev": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.24.0-canary-efb381bbf-20230505", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", + "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "peer": true + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "peer": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "peer": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sinon": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", + "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", + "deprecated": "16.1.1", + "dev": true, + "dependencies": { + "diff": "^3.1.0", + "formatio": "1.2.0", + "lolex": "^1.6.0", + "native-promise-only": "^0.8.1", + "path-to-regexp": "^1.7.0", + "samsam": "^1.1.3", + "text-encoding": "0.6.4", + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.1.103" + } + }, + "node_modules/sinon/node_modules/lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "peer": true + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/spawn-wrap/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "peer": true + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true, + "peer": true + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "dev": true, + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "dev": true, + "peer": true, + "dependencies": { + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/temp-fs": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", + "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", + "dev": true, + "dependencies": { + "rimraf": "~2.5.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/temp-fs/node_modules/rimraf": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", + "dev": true, + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/terser": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz", + "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "deprecated": "no longer maintained", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true, + "peer": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "peer": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "peer": true + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-jest": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-loader": { + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-mockito": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", + "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.5" + } + }, + "node_modules/ts-node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-node/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "peer": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "peer": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "dev": true, + "peer": true + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "peer": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true, + "peer": true + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true + }, + "@babel/core": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", + "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.22.20", + "@babel/helpers": "^7.22.15", + "@babel/parser": "^7.22.16", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.20", + "@babel/types": "^7.22.19", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "requires": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.23.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", + "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true + } + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + } + }, + "@babel/helpers": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", + "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + } + }, + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-export-default-from": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.23.3.tgz", + "integrity": "sha512-Q23MpLZfSGZL1kU7fWqV262q65svLSCIP5kZ/JCW/rKTCm/FrLjpvEd2kfUYMVeHh4QhV/xzyoRAHWrAZJrE3Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-default-from": "^7.23.3" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "peer": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "peer": true, + "requires": {} + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-default-from": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.23.3.tgz", + "integrity": "sha512-KeENO5ck1IeZ/l2lFZNy+mpobV3D2Zy5C1YFnWm+YuY5mQiAWc4yAp13dqgguwsBsFVLh4LPCEqCa5qW13N+hw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-flow": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", + "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", + "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "peer": true + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", + "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.23.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", + "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dev": true, + "peer": true, + "requires": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", + "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz", + "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", + "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", + "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "dev": true, + "peer": true, + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.8", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true + } } }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "@babel/preset-flow": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.23.3.tgz", + "integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==", "dev": true, + "peer": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-flow-strip-types": "^7.23.3" } }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, + "peer": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" } }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "@babel/preset-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", + "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", "dev": true, + "peer": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3" } }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "@babel/register": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", + "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", "dev": true, + "peer": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "peer": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "peer": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "peer": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "peer": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "peer": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } } }, - "@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", "dev": true, + "peer": true + }, + "@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "peer": true, "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { @@ -477,20 +17190,20 @@ } }, "@babel/traverse": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", - "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", - "debug": "^4.1.0", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "dependencies": { @@ -503,13 +17216,13 @@ } }, "@babel/types": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", - "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.19", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -525,6 +17238,31 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -580,6 +17318,23 @@ "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", "dev": true }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "peer": true + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "peer": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -603,6 +17358,13 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "dev": true, + "peer": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -668,124 +17430,289 @@ "dev": true }, "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "requires": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "requires": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true + }, + "jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + } + } + }, + "@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.6.3" } }, "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^28.1.3" + "jest-mock": "^29.7.0" } }, "@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "requires": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" } }, "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "requires": { - "jest-get-type": "^28.0.2" + "jest-get-type": "^29.6.3" } }, "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" } }, "@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" } }, "@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -793,94 +17720,168 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + } + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + } } }, "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "requires": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.27.8" } }, "@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.13", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, - "@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "dependencies": { + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + } } }, "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "requires": { - "@jest/schemas": "^28.1.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -964,19 +17965,536 @@ } }, "@react-native-async-storage/async-storage": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.19.3.tgz", - "integrity": "sha512-CwGfoHCWdPOTPS+2fW6YRE1fFBpT9++ahLEroX5hkgwyoQ+TkmjOaUxixdEIoVua9Pz5EF2pGOIJzqOTMWfBlA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz", + "integrity": "sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==", "dev": true, "requires": { "merge-options": "^3.0.4" } }, + "@react-native-community/cli": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", + "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-clean": "12.3.2", + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-doctor": "12.3.2", + "@react-native-community/cli-hermes": "12.3.2", + "@react-native-community/cli-plugin-metro": "12.3.2", + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-types": "12.3.2", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "peer": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "@react-native-community/cli-clean": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", + "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0" + } + }, + "@react-native-community/cli-config": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", + "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + } + }, + "@react-native-community/cli-debugger-ui": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", + "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", + "dev": true, + "peer": true, + "requires": { + "serve-static": "^1.13.1" + } + }, + "@react-native-community/cli-doctor": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", + "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "@react-native-community/cli-hermes": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", + "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5" + } + }, + "@react-native-community/cli-platform-android": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", + "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" + } + }, + "@react-native-community/cli-platform-ios": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", + "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-tools": "12.3.2", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" + } + }, + "@react-native-community/cli-plugin-metro": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", + "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==", + "dev": true, + "peer": true + }, + "@react-native-community/cli-server-api": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", + "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "peer": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "peer": true, + "requires": {} + } + } + }, + "@react-native-community/cli-tools": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", + "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", + "dev": true, + "peer": true, + "requires": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "@react-native-community/cli-types": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", + "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", + "dev": true, + "peer": true, + "requires": { + "joi": "^17.2.1" + } + }, "@react-native-community/netinfo": { "version": "5.9.10", "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", - "dev": true + "dev": true, + "requires": {} + }, + "@react-native/assets-registry": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", + "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "dev": true, + "peer": true + }, + "@react-native/babel-plugin-codegen": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", + "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", + "dev": true, + "peer": true, + "requires": { + "@react-native/codegen": "0.73.3" + } + }, + "@react-native/babel-preset": { + "version": "0.73.21", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", + "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.73.4", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + } + }, + "@react-native/codegen": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "dev": true, + "peer": true, + "requires": { + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" + } + }, + "@react-native/community-cli-plugin": { + "version": "0.73.16", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.16.tgz", + "integrity": "sha512-eNH3v3qJJF6f0n/Dck90qfC9gVOR4coAXMTdYECO33GfgjTi+73vf/SBqlXw9HICH/RNZYGPM3wca4FRF7TYeQ==", + "dev": true, + "peer": true, + "requires": { + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native/dev-middleware": "0.73.7", + "@react-native/metro-babel-transformer": "0.73.15", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.3", + "metro-config": "^0.80.3", + "metro-core": "^0.80.3", + "node-fetch": "^2.2.0", + "readline": "^1.3.0" + } + }, + "@react-native/debugger-frontend": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", + "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "dev": true, + "peer": true + }, + "@react-native/dev-middleware": { + "version": "0.73.7", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz", + "integrity": "sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg==", + "dev": true, + "peer": true, + "requires": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.73.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^1.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "open": "^7.0.3", + "serve-static": "^1.13.1", + "temp-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "peer": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + } + } + }, + "@react-native/gradle-plugin": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", + "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "dev": true, + "peer": true + }, + "@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "dev": true, + "peer": true + }, + "@react-native/metro-babel-transformer": { + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.21", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + } + }, + "@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "dev": true, + "peer": true + }, + "@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "dev": true, + "peer": true, + "requires": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + } }, "@rollup/plugin-commonjs": { "version": "11.1.0", @@ -1017,10 +18535,34 @@ "picomatch": "^2.2.2" } }, + "@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "peer": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "peer": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "peer": true + }, "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "@sinonjs/commons": { @@ -1033,12 +18575,23 @@ } }, "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "^3.0.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } } }, "@sinonjs/formatio": { @@ -1080,6 +18633,38 @@ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "optional": true, + "peer": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true, + "peer": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true, + "peer": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true, + "peer": true + }, "@types/babel__core": { "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", @@ -1242,12 +18827,6 @@ "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", "dev": true }, - "@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true - }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -1565,6 +19144,16 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "peer": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1595,13 +19184,15 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true + "dev": true, + "requires": {} }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "8.2.0", @@ -1644,6 +19235,20 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "dev": true, + "peer": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-escapes": { @@ -1663,6 +19268,37 @@ } } }, + "ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "dev": true, + "peer": true, + "requires": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1688,6 +19324,13 @@ "picomatch": "^2.0.4" } }, + "appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "dev": true, + "peer": true + }, "append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -1730,6 +19373,13 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "peer": true + }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -1751,6 +19401,30 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "peer": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true, + "peer": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1769,20 +19443,13 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, - "babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", "dev": true, - "requires": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } + "peer": true, + "requires": {} }, "babel-plugin-istanbul": { "version": "6.1.1", @@ -1797,16 +19464,56 @@ "test-exclude": "^6.0.0" } }, - "babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "babel-plugin-polyfill-corejs2": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", + "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", "dev": true, + "peer": true, "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.5.0", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.5.0" + } + }, + "babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/plugin-syntax-flow": "^7.12.1" } }, "babel-preset-current-node-syntax": { @@ -1829,22 +19536,19 @@ "@babel/plugin-syntax-top-level-await": "^7.8.3" } }, - "babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "peer": true + }, "base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -1866,11 +19570,17 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "peer": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } }, "body-parser": { "version": "1.20.2", @@ -1953,15 +19663,15 @@ "dev": true }, "browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" } }, "browserstack": { @@ -2034,6 +19744,17 @@ "node-int64": "^0.4.0" } }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "peer": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2103,6 +19824,35 @@ "get-intrinsic": "^1.0.2" } }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "peer": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "peer": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "peer": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2116,9 +19866,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", + "version": "1.0.30001585", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", + "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", "dev": true }, "caseless": { @@ -2180,12 +19930,49 @@ "readdirp": "~3.6.0" } }, + "chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + } + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "chromium-edge-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", + "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "peer": true + } + } + }, "ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -2204,6 +19991,23 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "peer": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "peer": true + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2215,6 +20019,25 @@ "wrap-ansi": "^7.0.0" } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "peer": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "peer": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2242,6 +20065,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "peer": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2251,11 +20081,12 @@ "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true + "command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true, + "peer": true }, "commondir": { "version": "1.0.1", @@ -2263,6 +20094,65 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "peer": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "peer": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "peer": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2316,6 +20206,16 @@ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true }, + "core-js-compat": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", + "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", + "dev": true, + "peer": true, + "requires": { + "browserslist": "^4.22.2" + } + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2332,6 +20232,50 @@ "vary": "^1" } }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "peer": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "peer": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "peer": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "peer": true + } + } + }, "coveralls": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", @@ -2345,6 +20289,184 @@ "request": "^2.88.2" } }, + "create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "dependencies": { + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true + }, + "jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + } + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true, + "peer": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2411,6 +20533,13 @@ "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", + "dev": true, + "peer": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2440,11 +20569,12 @@ "mimic-response": "^2.0.0" } }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "requires": {} }, "deep-eql": { "version": "4.1.3", @@ -2476,18 +20606,47 @@ "strip-bom": "^4.0.0" } }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "peer": true, + "requires": { + "clone": "^1.0.2" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, + "denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "dev": true, + "peer": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, + "deprecated-react-native-prop-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", + "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", + "dev": true, + "peer": true, + "requires": { + "@react-native/normalize-colors": "^0.73.0", + "invariant": "^2.2.4", + "prop-types": "^15.8.1" + } + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -2513,9 +20672,9 @@ "dev": true }, "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, "dir-glob": { @@ -2580,15 +20739,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.526", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.526.tgz", - "integrity": "sha512-tjjTMjmZAx1g6COrintLTa2/jcafYKxKoiEkdQOrVdbLaHh2wCt2nsAF8ZHweezkrP+dl/VG9T5nabcYoo0U5Q==", + "version": "1.4.661", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.661.tgz", + "integrity": "sha512-AFg4wDHSOk5F+zA8aR+SVIOabu7m0e7BiJnigCvPXzIGy731XENw/lmNxTySpVFtkFEy+eyt4oHhh5FF3NjQNw==", "dev": true }, "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true }, "emoji-regex": { @@ -2625,7 +20784,8 @@ "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -2657,6 +20817,13 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, + "envinfo": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", + "dev": true, + "peer": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2666,6 +20833,27 @@ "is-arrayish": "^0.2.1" } }, + "error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "peer": true, + "requires": { + "stackframe": "^1.3.4" + } + }, + "errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dev": true, + "peer": true, + "requires": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + } + }, "es-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", @@ -2921,6 +21109,13 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "peer": true + }, "event-stream": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", @@ -2936,6 +21131,13 @@ "through": "~2.3.1" } }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "peer": true + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -2972,16 +21174,16 @@ "dev": true }, "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" } }, "extend": { @@ -3033,6 +21235,16 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-xml-parser": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", + "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", + "dev": true, + "peer": true, + "requires": { + "strnum": "^1.0.5" + } + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -3154,6 +21366,12 @@ "path-exists": "^4.0.0" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, "flat-cache": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", @@ -3171,6 +21389,20 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "dev": true, + "peer": true + }, + "flow-parser": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", + "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "dev": true, + "peer": true + }, "follow-redirects": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", @@ -3213,6 +21445,13 @@ "samsam": "1.x" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "peer": true + }, "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -3393,12 +21632,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3461,11 +21694,47 @@ } }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hermes-estree": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", + "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", + "dev": true, + "peer": true + }, + "hermes-parser": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", + "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", + "dev": true, + "peer": true, + "requires": { + "hermes-estree": "0.15.0" + } + }, + "hermes-profile-transformer": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", + "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "dev": true, + "peer": true, + "requires": { + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "peer": true + } + } + }, "html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -3552,12 +21821,29 @@ "safer-buffer": ">= 2.1.2 < 3.0.0" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "peer": true + }, "ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, + "image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "dev": true, + "peer": true, + "requires": { + "queue": "6.0.2" + } + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3606,6 +21892,23 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "peer": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true, + "peer": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3630,6 +21933,20 @@ "has": "^1.0.3" } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "peer": true + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "peer": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3657,6 +21974,13 @@ "is-extglob": "^2.1.1" } }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "peer": true + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -3681,6 +22005,16 @@ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "peer": true, + "requires": { + "isobject": "^3.0.1" + } + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -3714,12 +22048,28 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "peer": true, + "requires": { + "is-docker": "^2.0.0" + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -3738,6 +22088,13 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "peer": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -3835,338 +22192,308 @@ } }, "jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "requires": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - }, - "dependencies": { - "jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - } + "jest-cli": "^29.7.0" } }, "jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "requires": { "execa": "^5.0.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, - "jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - } - }, - "jest-environment-jsdom": { + "jest-cli": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", - "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.7.0", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", "jest-util": "^29.7.0", - "jsdom": "^20.0.0" + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" }, "dependencies": { - "@jest/environment": { + "@jest/transform": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "requires": { - "@jest/fake-timers": "^29.7.0", + "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" } }, - "@jest/fake-timers": { + "babel-jest": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "requires": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" } }, - "@jest/schemas": { + "babel-plugin-jest-hoist": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "requires": { - "@sinclair/typebox": "^0.27.8" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" } }, - "@jest/types": { + "babel-preset-jest": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "requires": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" } }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0" - } + "optional": true, + "peer": true }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-message-util": { + "jest-config": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", + "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" + "strip-json-comments": "^3.1.1" } }, - "jest-util": { + "jest-haste-map": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "requires": { "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" } }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "optional": true, + "peer": true, "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" } } } }, + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + } + }, + "jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + } + }, "jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" } }, "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true }, - "jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, "jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "requires": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" } }, "jest-localstorage-mock": { @@ -4176,180 +22503,380 @@ "dev": true }, "jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" } }, "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" } }, "jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true + "dev": true, + "requires": {} }, "jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "requires": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", + "resolve.exports": "^2.0.0", "slash": "^3.0.0" + }, + "dependencies": { + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + } } }, "jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "requires": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "dependencies": { + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + } } }, "jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "requires": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "emittery": "^0.10.2", + "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" + }, + "dependencies": { + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + } } }, "jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" + }, + "dependencies": { + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + } } }, "jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "requires": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^28.1.3", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "dependencies": { + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + } } }, "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "requires": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -4358,17 +22885,17 @@ } }, "jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "requires": { - "@jest/types": "^28.1.3", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^28.1.3" + "pretty-format": "^29.7.0" }, "dependencies": { "camelcase": { @@ -4380,28 +22907,29 @@ } }, "jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "requires": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "requires": { "@types/node": "*", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4417,6 +22945,20 @@ } } }, + "joi": { + "version": "17.12.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", + "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", + "dev": true, + "peer": true, + "requires": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4439,6 +22981,62 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, + "jsc-android": { + "version": "250231.0.0", + "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", + "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", + "dev": true, + "peer": true + }, + "jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "dev": true, + "peer": true + }, + "jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "peer": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, "jsdom": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", @@ -4516,6 +23114,13 @@ "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "peer": true + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -4665,7 +23270,8 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true + "dev": true, + "requires": {} }, "karma-chrome-launcher": { "version": "2.2.0", @@ -4706,14 +23312,34 @@ } }, "karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", + "integrity": "sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==", "dev": true, "requires": { "glob": "^7.1.3", - "minimatch": "^3.0.4", + "minimatch": "^9.0.3", "webpack-merge": "^4.1.5" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "keyv": { @@ -4725,6 +23351,13 @@ "json-buffer": "3.0.1" } }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "peer": true + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4753,6 +23386,36 @@ "type-check": "~0.4.0" } }, + "lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "peer": true, + "requires": { + "debug": "^2.6.9", + "marky": "^1.2.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + } + } + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4780,6 +23443,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "peer": true + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -4798,12 +23468,29 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true, + "peer": true + }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, "log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -4817,6 +23504,123 @@ "streamroller": "^3.1.5" } }, + "logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "dependencies": { + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "peer": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "peer": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "peer": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "peer": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "lolex": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", @@ -4826,6 +23630,16 @@ "@sinonjs/commons": "^1.7.0" } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "peer": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -4883,12 +23697,26 @@ "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, + "marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "dev": true, + "peer": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "dev": true, + "peer": true + }, "merge-options": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", @@ -4910,6 +23738,376 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "metro": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.5.tgz", + "integrity": "sha512-OE/CGbOgbi8BlTN1QqJgKOBaC27dS0JBQw473JcivrpgVnqIsluROA7AavEaTVUrB9wPUZvoNVDROn5uiM2jfw==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.18.2", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.80.5", + "metro-cache": "0.80.5", + "metro-cache-key": "0.80.5", + "metro-config": "0.80.5", + "metro-core": "0.80.5", + "metro-file-map": "0.80.5", + "metro-resolver": "0.80.5", + "metro-runtime": "0.80.5", + "metro-source-map": "0.80.5", + "metro-symbolicate": "0.80.5", + "metro-transform-plugins": "0.80.5", + "metro-transform-worker": "0.80.5", + "mime-types": "^2.1.27", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "rimraf": "^3.0.2", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "peer": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "hermes-estree": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", + "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", + "dev": true, + "peer": true + }, + "hermes-parser": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", + "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "dev": true, + "peer": true, + "requires": { + "hermes-estree": "0.18.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "peer": true + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "peer": true, + "requires": {} + } + } + }, + "metro-babel-transformer": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.5.tgz", + "integrity": "sha512-sxH6hcWCorhTbk4kaShCWsadzu99WBL4Nvq4m/sDTbp32//iGuxtAnUK+ZV+6IEygr2u9Z0/4XoZ8Sbcl71MpA==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.20.0", + "hermes-parser": "0.18.2", + "nullthrows": "^1.1.1" + }, + "dependencies": { + "hermes-estree": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", + "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", + "dev": true, + "peer": true + }, + "hermes-parser": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", + "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "dev": true, + "peer": true, + "requires": { + "hermes-estree": "0.18.2" + } + } + } + }, + "metro-cache": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.5.tgz", + "integrity": "sha512-2u+dQ4PZwmC7eZo9uMBNhQQMig9f+w4QWBZwXCdVy/RYOHM0eObgGdMEOwODo73uxie82T9lWzxr3aZOZ+Nqtw==", + "dev": true, + "peer": true, + "requires": { + "metro-core": "0.80.5", + "rimraf": "^3.0.2" + } + }, + "metro-cache-key": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.5.tgz", + "integrity": "sha512-fr3QLZUarsB3tRbVcmr34kCBsTHk0Sh9JXGvBY/w3b2lbre+Lq5gtgLyFElHPecGF7o4z1eK9r3ubxtScHWcbA==", + "dev": true, + "peer": true + }, + "metro-config": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.5.tgz", + "integrity": "sha512-elqo/lwvF+VjZ1OPyvmW/9hSiGlmcqu+rQvDKw5F5WMX48ZC+ySTD1WcaD7e97pkgAlJHVYqZ98FCjRAYOAFRQ==", + "dev": true, + "peer": true, + "requires": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "jest-validate": "^29.6.3", + "metro": "0.80.5", + "metro-cache": "0.80.5", + "metro-core": "0.80.5", + "metro-runtime": "0.80.5" + } + }, + "metro-core": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.5.tgz", + "integrity": "sha512-vkLuaBhnZxTVpaZO8ZJVEHzjaqSXpOdpAiztSZ+NDaYM6jEFgle3/XIbLW91jTSf2+T8Pj5yB1G7KuOX+BcVwg==", + "dev": true, + "peer": true, + "requires": { + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.80.5" + } + }, + "metro-file-map": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.5.tgz", + "integrity": "sha512-bKCvJ05drjq6QhQxnDUt3I8x7bTcHo3IIKVobEr14BK++nmxFGn/BmFLRzVBlghM6an3gqwpNEYxS5qNc+VKcg==", + "dev": true, + "peer": true, + "requires": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + } + } + }, + "metro-minify-terser": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.5.tgz", + "integrity": "sha512-S7oZLLcab6YXUT6jYFX/ZDMN7Fq6xBGGAG8liMFU1UljX6cTcEC2u+UIafYgCLrdVexp/+ClxrIetVPZ5LtL/g==", + "dev": true, + "peer": true, + "requires": { + "terser": "^5.15.0" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "terser": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + } + } + }, + "metro-resolver": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.5.tgz", + "integrity": "sha512-haJ/Hveio3zv/Fr4eXVdKzjUeHHDogYok7OpRqPSXGhTXisNXB+sLN7CpcUrCddFRUDLnVaqQOYwhYsFndgUwA==", + "dev": true, + "peer": true + }, + "metro-runtime": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.5.tgz", + "integrity": "sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.0.0" + } + }, + "metro-source-map": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.5.tgz", + "integrity": "sha512-DwSF4l03mKPNqCtyQ6K23I43qzU1BViAXnuH81eYWdHglP+sDlPpY+/7rUahXEo6qXEHXfAJgVoo1sirbXbmsQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.80.5", + "nullthrows": "^1.1.1", + "ob1": "0.80.5", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "peer": true + } + } + }, + "metro-symbolicate": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.5.tgz", + "integrity": "sha512-IsM4mTYvmo9JvIqwEkCZ5+YeDVPST78Q17ZgljfLdHLSpIivOHp9oVoiwQ/YGbLx0xRHRIS/tKiXueWBnj3UWA==", + "dev": true, + "peer": true, + "requires": { + "invariant": "^2.2.4", + "metro-source-map": "0.80.5", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "peer": true + } + } + }, + "metro-transform-plugins": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.5.tgz", + "integrity": "sha512-7IdlTqK/k5+qE3RvIU5QdCJUPk4tHWEqgVuYZu8exeW+s6qOJ66hGIJjXY/P7ccucqF+D4nsbAAW5unkoUdS6g==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "nullthrows": "^1.1.1" + } + }, + "metro-transform-worker": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.5.tgz", + "integrity": "sha512-Q1oM7hfP+RBgAtzRFBDjPhArELUJF8iRCZ8OidqCpYzQJVGuJZ7InSnIf3hn1JyqiUQwv2f1LXBO78i2rAjzyA==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "metro": "0.80.5", + "metro-babel-transformer": "0.80.5", + "metro-cache": "0.80.5", + "metro-cache-key": "0.80.5", + "metro-minify-terser": "0.80.5", + "metro-source-map": "0.80.5", + "metro-transform-plugins": "0.80.5", + "nullthrows": "^1.1.1" + } + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -4977,43 +24175,61 @@ } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "ms": "2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -5022,52 +24238,92 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { - "minimist": "0.0.8" + "randombytes": "^2.1.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true } } }, @@ -5088,6 +24344,12 @@ "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", @@ -5131,6 +24393,13 @@ "path-to-regexp": "^1.7.0" } }, + "nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "dev": true, + "peer": true + }, "nock": { "version": "11.9.1", "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", @@ -5144,6 +24413,60 @@ "propagate": "^2.0.0" } }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "peer": true + }, + "node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dev": true, + "peer": true, + "requires": { + "minimatch": "^3.0.2" + } + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "peer": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "peer": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "peer": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "peer": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5160,11 +24483,18 @@ } }, "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "dev": true, + "peer": true + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5186,6 +24516,13 @@ "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", "dev": true }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, + "peer": true + }, "nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", @@ -5362,6 +24699,13 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "ob1": { + "version": "0.80.5", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", + "integrity": "sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w==", + "dev": true, + "peer": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5383,6 +24727,13 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "peer": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5401,6 +24752,25 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dev": true, + "peer": true, + "requires": { + "is-wsl": "^1.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "peer": true + } + } + }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -5415,6 +24785,24 @@ "type-check": "^0.4.0" } }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "peer": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5568,6 +24956,13 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "peer": true + }, "pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -5644,13 +25039,12 @@ } }, "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -5663,6 +25057,13 @@ } } }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "peer": true + }, "process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -5672,6 +25073,16 @@ "fromentries": "^1.2.0" } }, + "promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dev": true, + "peer": true, + "requires": { + "asap": "~2.0.6" + } + }, "promise-polyfill": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", @@ -5688,6 +25099,27 @@ "sisteransi": "^1.0.5" } }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "peer": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "peer": true + } + } + }, "propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -5715,6 +25147,12 @@ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, + "pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -5739,6 +25177,16 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "peer": true, + "requires": { + "inherits": "~2.0.3" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5760,44 +25208,302 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "peer": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-devtools-core": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", + "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "dev": true, + "peer": true, + "requires": { + "shell-quote": "^1.6.1", + "ws": "^7" + }, + "dependencies": { + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "peer": true, + "requires": {} + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "react-native": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.4.tgz", + "integrity": "sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==", + "dev": true, + "peer": true, + "requires": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native-community/cli": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", + "@react-native/assets-registry": "0.73.1", + "@react-native/codegen": "0.73.3", + "@react-native/community-cli-plugin": "0.73.16", + "@react-native/gradle-plugin": "0.73.4", + "@react-native/js-polyfills": "0.73.1", + "@react-native/normalize-colors": "0.73.2", + "@react-native/virtualized-lists": "0.73.4", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "base64-js": "^1.5.1", + "chalk": "^4.0.0", + "deprecated-react-native-prop-types": "^5.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "jsc-android": "^250231.0.0", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.80.3", + "metro-source-map": "^0.80.3", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "pretty-format": "^26.5.2", + "promise": "^8.3.0", + "react-devtools-core": "^4.27.7", + "react-refresh": "^0.14.0", + "react-shallow-renderer": "^16.15.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.24.0-canary-efb381bbf-20230505", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.2", + "yargs": "^17.6.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "peer": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, + "peer": true + }, + "ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "peer": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "peer": true + }, + "react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, + "peer": true, + "requires": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "peer": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "dev": true, + "peer": true + }, + "recast": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "dev": true, + "peer": true, + "requires": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "peer": true + }, + "regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "peer": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "peer": true + }, + "regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", "dev": true, + "peer": true, "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "jsesc": "~0.5.0" }, "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } + "peer": true } } }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -5896,11 +25602,22 @@ "dev": true }, "resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "peer": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6063,6 +25780,16 @@ "xmlchars": "^2.2.0" } }, + "scheduler": { + "version": "0.24.0-canary-efb381bbf-20230505", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", + "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", + "dev": true, + "peer": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, "schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -6083,6 +25810,70 @@ "lru-cache": "^6.0.0" } }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "peer": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "peer": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "peer": true + } + } + }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "dev": true, + "peer": true + }, "serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -6092,6 +25883,19 @@ "randombytes": "^2.1.0" } }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "peer": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6104,6 +25908,16 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "peer": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6119,6 +25933,13 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "peer": true + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -6172,6 +25993,54 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "peer": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "peer": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "peer": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true + } + } + }, "socket.io": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", @@ -6200,7 +26069,8 @@ "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -6316,6 +26186,32 @@ } } }, + "stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "peer": true + }, + "stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "peer": true, + "requires": { + "type-fest": "^0.7.1" + }, + "dependencies": { + "type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "peer": true + } + } + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -6342,6 +26238,16 @@ "fs-extra": "^8.1.0" } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "peer": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6390,6 +26296,20 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true, + "peer": true + }, + "sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "dev": true, + "peer": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6399,16 +26319,6 @@ "has-flag": "^4.0.0" } }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -6427,6 +26337,35 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, + "temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "dev": true, + "peer": true, + "requires": { + "rimraf": "~2.6.2" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "peer": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "peer": true + }, "temp-fs": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", @@ -6447,16 +26386,6 @@ } } }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, "terser": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", @@ -6571,12 +26500,72 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true, + "peer": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "peer": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "peer": true + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "peer": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "peer": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -6633,18 +26622,18 @@ } }, "ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, "requires": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", + "jest-util": "^29.0.0", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" } }, @@ -6789,6 +26778,38 @@ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==" }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "peer": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "peer": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "peer": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "peer": true + }, "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -6830,6 +26851,13 @@ "requires-port": "^1.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "peer": true + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6841,15 +26869,31 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "optional": true, + "peer": true + }, "v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } } }, "vary": { @@ -6869,6 +26913,13 @@ "extsprintf": "^1.2.0" } }, + "vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "dev": true, + "peer": true + }, "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -6903,6 +26954,16 @@ "graceful-fs": "^4.1.2" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "peer": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -6973,6 +27034,13 @@ "iconv-lite": "0.6.3" } }, + "whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true, + "peer": true + }, "whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -7004,6 +27072,12 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7035,7 +27109,8 @@ "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "4.0.0", @@ -7049,6 +27124,13 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "peer": true + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7061,6 +27143,13 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "peer": true + }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -7082,6 +27171,32 @@ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 76bb1e215..668bd6d0a 100644 --- a/package.json +++ b/package.json @@ -110,13 +110,12 @@ "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", - "bluebird": "^3.4.6", "chai": "^4.2.0", "coveralls": "^3.0.2", "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", - "jest": "^28.1.3", + "jest": "^29.7.0", "jest-environment-jsdom": "^29.0.0", "jest-localstorage-mock": "^2.4.22", "json-loader": "^0.5.4", @@ -125,9 +124,9 @@ "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.1.1", "karma-mocha": "^1.3.0", - "karma-webpack": "^5.0.0", + "karma-webpack": "^5.0.1", "lodash": "^4.17.11", - "mocha": "^5.2.0", + "mocha": "^10.2.0", "mocha-lcov-reporter": "^1.3.0", "nise": "^1.4.10", "nock": "11.9.1", @@ -138,7 +137,7 @@ "rollup-plugin-terser": "^5.3.0", "rollup-plugin-typescript2": "^0.27.1", "sinon": "^2.3.1", - "ts-jest": "^28.0.8", + "ts-jest": "^29.1.2", "ts-loader": "^9.3.1", "ts-mockito": "^2.6.1", "ts-node": "^8.10.2", From e765bd409b575e5cab9ff0eedd9556504d92d929 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 8 Feb 2024 20:29:47 +0600 Subject: [PATCH 055/200] [FSSDK-9965] bump minimist and karma-mocha (#898) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 32 +++++++++----------------------- package.json | 2 +- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85fbaaa47..9cf93d630 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "karma-browserstack-launcher": "^1.5.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.1.1", - "karma-mocha": "^1.3.0", + "karma-mocha": "^2.0.1", "karma-webpack": "^5.0.1", "lodash": "^4.17.11", "mocha": "^10.2.0", @@ -10333,20 +10333,14 @@ } }, "node_modules/karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", + "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", "dev": true, "dependencies": { - "minimist": "1.2.0" + "minimist": "^1.2.3" } }, - "node_modules/karma-mocha/node_modules/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", - "dev": true - }, "node_modules/karma-webpack": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", @@ -23295,20 +23289,12 @@ } }, "karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", + "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", "dev": true, "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", - "dev": true - } + "minimist": "^1.2.3" } }, "karma-webpack": { diff --git a/package.json b/package.json index 668bd6d0a..eb6c457dd 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "karma-browserstack-launcher": "^1.5.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.1.1", - "karma-mocha": "^1.3.0", + "karma-mocha": "^2.0.1", "karma-webpack": "^5.0.1", "lodash": "^4.17.11", "mocha": "^10.2.0", From 23697c938940ce933c6e47656cc1ba8beb7cec96 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 8 Feb 2024 21:08:52 +0600 Subject: [PATCH 056/200] [FSSDK-9966] use coveralls-next instead of coveralls (#900) --- package-lock.json | 677 ++++++---------------------------------------- package.json | 2 +- 2 files changed, 84 insertions(+), 595 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9cf93d630..b17e663a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", "chai": "^4.2.0", - "coveralls": "^3.0.2", + "coveralls-next": "^4.2.0", "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", @@ -5242,24 +5242,6 @@ "dev": true, "peer": true }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -5305,21 +5287,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -5467,15 +5434,6 @@ "node": "^4.5.0 || >= 5.9" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -5890,12 +5848,6 @@ } ] }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "node_modules/chai": { "version": "4.3.8", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", @@ -6336,7 +6288,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/cors": { "version": "2.8.5", @@ -6405,23 +6358,50 @@ "node": ">=4" } }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "node_modules/coveralls-next": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", + "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", "dev": true, "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" + "form-data": "4.0.0", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.7" }, "bin": { "coveralls": "bin/coveralls.js" }, "engines": { - "node": ">=6" + "node": ">=14" + } + }, + "node_modules/coveralls-next/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/coveralls-next/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/coveralls-next/node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/create-jest": { @@ -6716,18 +6696,6 @@ "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", "dev": true }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -7008,16 +6976,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -7639,15 +7597,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7943,27 +7892,18 @@ "node": ">=8.0.0" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/formatio": { @@ -8145,15 +8085,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8239,29 +8170,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8443,21 +8351,6 @@ "node": ">= 6" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -8882,12 +8775,6 @@ "node": ">=0.10.0" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -10006,12 +9893,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, "node_modules/jsc-android": { "version": "250231.0.0", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", @@ -10117,20 +9998,6 @@ } } }, - "node_modules/jsdom/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/jsdom/node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -10227,21 +10094,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -12102,15 +11954,6 @@ "node": ">=6" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/ob1": { "version": "0.80.5", "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", @@ -12435,12 +12278,6 @@ "through": "~2.3" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -12742,15 +12579,6 @@ "node": ">=0.9" } }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -13170,48 +12998,6 @@ "node": ">=4" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13991,31 +13777,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -14559,19 +14320,6 @@ "node": ">=0.6" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -14726,24 +14474,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -15010,20 +14740,6 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", @@ -19374,21 +19090,6 @@ "dev": true, "peer": true }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -19425,18 +19126,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, "babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -19549,15 +19238,6 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -19865,12 +19545,6 @@ "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "chai": { "version": "4.3.8", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", @@ -20214,7 +19888,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "dev": true, + "peer": true }, "cors": { "version": "2.8.5", @@ -20270,17 +19945,40 @@ } } }, - "coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "coveralls-next": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", + "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", "dev": true, "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" + "form-data": "4.0.0", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.7" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + } } }, "create-jest": { @@ -20501,15 +20199,6 @@ "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", "dev": true }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -20716,16 +20405,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -21186,12 +20865,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -21413,20 +21086,14 @@ "signal-exit": "^3.0.2" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -21553,15 +21220,6 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -21626,22 +21284,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -21779,17 +21421,6 @@ "debug": "4" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -22089,12 +21720,6 @@ "dev": true, "peer": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, "istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -22969,12 +22594,6 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, "jsc-android": { "version": "250231.0.0", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", @@ -23065,17 +22684,6 @@ "xml-name-validator": "^4.0.0" }, "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, "tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -23159,18 +22767,6 @@ "graceful-fs": "^4.1.6" } }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -24679,12 +24275,6 @@ } } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, "ob1": { "version": "0.80.5", "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", @@ -24924,12 +24514,6 @@ "through": "~2.3" } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -25151,12 +24735,6 @@ "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -25499,42 +25077,6 @@ "es6-error": "^4.0.1" } }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -26138,23 +25680,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -26588,16 +26113,6 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -26698,21 +26213,6 @@ } } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -26888,17 +26388,6 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", diff --git a/package.json b/package.json index eb6c457dd..50e0c4d42 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", "chai": "^4.2.0", - "coveralls": "^3.0.2", + "coveralls-next": "^4.2.0", "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", From d4b7ff8e36e3439909285e11e32b21aa10bb0c5f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 8 Feb 2024 23:07:47 +0600 Subject: [PATCH 057/200] [FSSDK-9966] bump follow-redirects (#901) --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b17e663a2..208a8cc16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7860,9 +7860,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true, "funding": [ { @@ -21071,9 +21071,9 @@ "peer": true }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true }, "foreground-child": { From 98bf936e07b556b5bfa4d650d0307b8c70486fca Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:59:05 -0500 Subject: [PATCH 058/200] [FSSDK-9987] fix: Conditional ODP instantiation (#902) * fix: do not instantiate ODP manager if explicit odpOptions.disabled: true * test: correct test for now `undefined` ODP Manager * fix: remove error log for fetchQualifiedSegments this method is often called automatically * revert: return error message to fQS. Plus some lint fixes * docs: update copyright notices --------- Co-authored-by: Mike Chu <michael.chu@optmizely.com> --- lib/index.browser.tests.js | 31 ++++++++++++++++++------------- lib/index.browser.ts | 12 +++++++++--- lib/index.node.ts | 14 ++++++++++---- lib/index.react_native.ts | 34 +++++++++++++++++----------------- lib/optimizely/index.ts | 27 +++++++++++++++++++-------- 5 files changed, 73 insertions(+), 45 deletions(-) diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 0d5d3c1c3..46814697b 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -1,11 +1,11 @@ /** - * Copyright 2016-2020, 2022-2023 Optimizely + * Copyright 2016-2020, 2022-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import logging, { getLogger } from './modules/logging/logger'; import { assert } from 'chai'; @@ -67,7 +68,7 @@ if (!global.window) { } } -const pause = (timeoutMilliseconds) => { +const pause = timeoutMilliseconds => { return new Promise(resolve => setTimeout(resolve, timeoutMilliseconds)); }; @@ -846,19 +847,19 @@ describe('javascript-sdk (Browser)', function() { const userAgentParser = { parseUserAgentInfo() { return { - os: { 'name': 'windows', 'version': '11' }, - device: { 'type': 'laptop', 'model': 'thinkpad' }, - } - } - } + os: { name: 'windows', version: '11' }, + device: { type: 'laptop', model: 'thinkpad' }, + }; + }, + }; const fakeRequestHandler = { - makeRequest: sinon.spy(function (requestUrl, headers, method, data) { + makeRequest: sinon.spy(function(requestUrl, headers, method, data) { return { abort: () => {}, responsePromise: Promise.resolve({ statusCode: 200 }), - } - }) + }; + }), }; const client = optimizelyFactory.createInstance({ @@ -1047,11 +1048,15 @@ describe('javascript-sdk (Browser)', function() { const readyData = await client.onReady(); assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); + assert.isUndefined(client.odpManager); + sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); - sinon.assert.calledWith(logger.error, 'ODP event send failed.'); - sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); + sinon.assert.calledWith( + logger.error, + optimizelyFactory.enums.ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING + ); }); it('should log a warning when attempting to use an event batch size other than 1', async () => { diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 6d16665b9..46ce0dbe6 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2019-2023 Optimizely + * Copyright 2016-2017, 2019-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import logHelper from './modules/logging/logger'; import { getLogger, setErrorHandler, getErrorHandler, LogLevel } from './modules/logging'; import { LocalStoragePendingEventsDispatcher } from './modules/event_processor'; @@ -125,6 +126,11 @@ const createInstance = function(config: Config): Client | null { notificationCenter, }; + const odpExplicitlyOff = config.odpOptions?.disabled === true; + if (odpExplicitlyOff) { + logger.info(enums.LOG_MESSAGES.ODP_DISABLED); + } + const optimizelyOptions: OptimizelyOptions = { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, @@ -135,8 +141,8 @@ const createInstance = function(config: Config): Client | null { ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, notificationCenter, - isValidInstance: isValidInstance, - odpManager: new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), + isValidInstance, + odpManager: odpExplicitlyOff ? undefined : new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), }; const optimizely = new Optimizely(optimizelyOptions); diff --git a/lib/index.node.ts b/lib/index.node.ts index 168ccc287..57d72e174 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -1,11 +1,11 @@ /**************************************************************************** - * Copyright 2016-2017, 2019-2023 Optimizely, Inc. and contributors * + * Copyright 2016-2017, 2019-2024 Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * - * https://www.apache.org/licenses/LICENSE-2.0 * + * https://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ + import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; import Optimizely from './optimizely'; import * as enums from './utils/enums'; @@ -101,6 +102,11 @@ const createInstance = function(config: Config): Client | null { const eventProcessor = createEventProcessor(eventProcessorConfig); + const odpExplicitlyOff = config.odpOptions?.disabled === true; + if (odpExplicitlyOff) { + logger.info(enums.LOG_MESSAGES.ODP_DISABLED); + } + const optimizelyOptions = { clientEngine: enums.NODE_CLIENT_ENGINE, ...config, @@ -111,8 +117,8 @@ const createInstance = function(config: Config): Client | null { ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, notificationCenter, - isValidInstance: isValidInstance, - odpManager: new NodeOdpManager({ logger, odpOptions: config.odpOptions }), + isValidInstance, + odpManager: odpExplicitlyOff ? undefined : new NodeOdpManager({ logger, odpOptions: config.odpOptions }), }; return new Optimizely(optimizelyOptions); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 205f088aa..7340a03e4 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2023, Optimizely + * Copyright 2019-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, - setLogHandler, - setLogLevel -} from './modules/logging'; + +import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; import * as enums from './utils/enums'; import Optimizely from './optimizely'; import configValidator from './utils/config_validator'; @@ -31,8 +25,7 @@ import eventProcessorConfigValidator from './utils/event_processor_config_valida import { createNotificationCenter } from './core/notification_center'; import { createEventProcessor } from './plugins/event_processor/index.react_native'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { createHttpPollingDatafileManager } from - './plugins/datafile_manager/react_native_http_polling_datafile_manager'; +import { createHttpPollingDatafileManager } from './plugins/datafile_manager/react_native_http_polling_datafile_manager'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import * as commonExports from './common_exports'; @@ -71,7 +64,7 @@ const createInstance = function(config: Config): Client | null { configValidator.validate(config); isValidInstance = true; // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex: any) { + } catch (ex) { logger.error(ex); } @@ -100,20 +93,27 @@ const createInstance = function(config: Config): Client | null { batchSize: eventBatchSize, maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, notificationCenter, - } + }; const eventProcessor = createEventProcessor(eventProcessorConfig); + const odpExplicitlyOff = config.odpOptions?.disabled === true; + if (odpExplicitlyOff) { + logger.info(enums.LOG_MESSAGES.ODP_DISABLED); + } + const optimizelyOptions = { clientEngine: enums.REACT_NATIVE_JS_CLIENT_ENGINE, ...config, eventProcessor, logger, errorHandler, - datafileManager: config.sdkKey ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, + datafileManager: config.sdkKey + ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) + : undefined, notificationCenter, isValidInstance: isValidInstance, - odpManager: new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: odpExplicitlyOff ? undefined : new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), }; // If client engine is react, convert it to react native. @@ -123,7 +123,7 @@ const createInstance = function(config: Config): Client | null { return new Optimizely(optimizelyOptions); // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { + } catch (e) { logger.error(e); return null; } @@ -157,4 +157,4 @@ export default { OptimizelyDecideOption, }; -export * from './export_types' +export * from './export_types'; diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 07b106003..edf3691a8 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2020-2023, Optimizely, Inc. and contributors * + * Copyright 2020-2024, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -178,10 +178,7 @@ export default class Optimizely implements Client { const eventProcessorStartedPromise = this.eventProcessor.start(); - const dependentPromises: Array<Promise<any>> = [ - projectConfigManagerReadyPromise, - eventProcessorStartedPromise, - ]; + const dependentPromises: Array<Promise<any>> = [projectConfigManagerReadyPromise, eventProcessorStartedPromise]; if (config.odpManager?.initPromise) { dependentPromises.push(config.odpManager.initPromise); @@ -778,7 +775,12 @@ export default class Optimizely implements Client { * type, or null if the feature key is invalid or * the variable key is invalid */ - getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): FeatureVariableValue { + getFeatureVariable( + featureKey: string, + variableKey: string, + userId: string, + attributes?: UserAttributes + ): FeatureVariableValue { try { if (!this.isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable'); @@ -1684,7 +1686,12 @@ export default class Optimizely implements Client { const projectConfig = this.projectConfigManager.getConfig(); if (this.odpManager != null && projectConfig != null) { this.odpManager.updateSettings( - new OdpConfig(projectConfig.publicKeyForOdp, projectConfig.hostForOdp, projectConfig.pixelUrlForOdp, projectConfig.allSegments) + new OdpConfig( + projectConfig.publicKeyForOdp, + projectConfig.hostForOdp, + projectConfig.pixelUrlForOdp, + projectConfig.allSegments + ) ); } } @@ -1758,6 +1765,10 @@ export default class Optimizely implements Client { options?: Array<OptimizelySegmentOption> ): Promise<string[] | null> { if (!this.odpManager) { + return null; + } + + if (!this.odpManager.enabled) { this.logger.error(ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING); return null; } @@ -1769,7 +1780,7 @@ export default class Optimizely implements Client { * @returns {string|undefined} Currently provisioned VUID from local ODP Manager or undefined if * ODP Manager has not been instantiated yet for any reason. */ - getVuid(): string | undefined { + public getVuid(): string | undefined { if (!this.odpManager) { this.logger?.error('Unable to get VUID - ODP Manager is not instantiated yet.'); return undefined; From d5e6a05666b4b68b5356bee0ccaf8f4a4ff33821 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Mon, 19 Feb 2024 08:51:00 -0500 Subject: [PATCH 059/200] [FSSDK-9987] chore: prep for 5.0.1 (#903) * ci: bump version to 5.0.1 * chore: update copyright year * doc: update changelog --------- Co-authored-by: Mike Chu <michael.chu@optmizely.com> --- CHANGELOG.md | 10 ++++++++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 4 ++-- lib/index.node.tests.js | 4 ++-- lib/utils/enums/index.ts | 6 +++--- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 4 ++-- 8 files changed, 23 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2241654e7..3bc1b79c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.0.1] - February 20, 2024 + +### Bug fixes +- Improved conditional ODP instantiation when `odpOptions.disabled: true` is used ([#902](https://github.com/optimizely/javascript-sdk/pull/902)) + +### Changed +- Updated Dependabot alerts ([#896](https://github.com/optimizely/javascript-sdk/pull/896)) +- Updated several devDependencies ([#898](https://github.com/optimizely/javascript-sdk/pull/898), [#900](https://github.com/optimizely/javascript-sdk/pull/900), [#901](https://github.com/optimizely/javascript-sdk/pull/901)) + + ## [5.0.0] - January 19, 2024 ### New Features diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 46814697b..c6905d593 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0'); + assert.equal(optlyInstance.clientVersion, '5.0.1'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index da60bf549..3c7594e64 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2021-2023 Optimizely + * Copyright 2021-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0'); + assert.equal(optlyInstance.clientVersion, '5.0.1'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 3d2fafddc..ef480cf8c 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2020, 2022-2023 Optimizely + * Copyright 2016-2020, 2022-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.0'); + assert.equal(optlyInstance.clientVersion, '5.0.1'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index ee05148f4..3456343e2 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2023, Optimizely, Inc. and contributors * + * Copyright 2016-2024 Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -223,8 +223,8 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '5.0.0'; -export const NODE_CLIENT_VERSION = '5.0.0'; +export const BROWSER_CLIENT_VERSION = '5.0.1'; +export const NODE_CLIENT_VERSION = '5.0.1'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 208a8cc16..9d131ca29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0", + "version": "5.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0", + "version": "5.0.1", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index 50e0c4d42..6e0fb5af8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.0", + "version": "5.0.1", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index fbdadfac2..ae42147a2 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, 2022-2023 Optimizely + * Copyright 2019-2020, 2022-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.0.0'); + expect(optlyInstance.clientVersion).toEqual('5.0.1'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From e938e3cdd833754215e8733bc55b2bb080902c54 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 23 Feb 2024 20:36:08 +0600 Subject: [PATCH 060/200] [FSSDK-9615] log error when datafile fetch request fails (#904) * [FSSDK-9615] log error when datafile fetch request fails * update copyright --- .../httpPollingDatafileManager.ts | 3 +- tests/httpPollingDatafileManager.spec.ts | 32 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/lib/modules/datafile-manager/httpPollingDatafileManager.ts index d71a4d3ef..a1d1829e3 100644 --- a/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ b/lib/modules/datafile-manager/httpPollingDatafileManager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -325,6 +325,7 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana if (isSuccessStatusCode(response.statusCode)) { return response.body; } + logger.error(`Datafile fetch request failed with status: ${response.statusCode}`); return ''; } diff --git a/tests/httpPollingDatafileManager.spec.ts b/tests/httpPollingDatafileManager.spec.ts index ffb8e565b..94439559d 100644 --- a/tests/httpPollingDatafileManager.spec.ts +++ b/tests/httpPollingDatafileManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import { DatafileManagerConfig } from '../lib/modules/datafile-manager/datafileM import { advanceTimersByTime, getTimerCount } from './testUtils'; import PersistentKeyValueCache from '../lib/modules/datafile-manager/persistentKeyValueCache'; + jest.mock('../lib/modules/datafile-manager/backoffController', () => { return jest.fn().mockImplementation(() => { const getDelayMock = jest.fn().mockImplementation(() => 0); @@ -32,6 +33,8 @@ jest.mock('../lib/modules/datafile-manager/backoffController', () => { }); import BackoffController from '../lib/modules/datafile-manager/backoffController'; +import { LoggerFacade, getLogger } from '../lib/modules/logging'; +import { resetCalls, spy, verify } from 'ts-mockito'; // Test implementation: // - Does not make any real requests: just resolves with queued responses (tests push onto queuedResponses) @@ -93,8 +96,19 @@ const testCache: PersistentKeyValueCache = { }; describe('httpPollingDatafileManager', () => { + + let spiedLogger: LoggerFacade; + + const loggerName = 'DatafileManager'; + + beforeAll(() => { + const actualLogger = getLogger(loggerName); + spiedLogger = spy(actualLogger); + }); + beforeEach(() => { jest.useFakeTimers(); + resetCalls(spiedLogger); }); let manager: TestDatafileManager; @@ -179,6 +193,22 @@ describe('httpPollingDatafileManager', () => { manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 1000, autoUpdate: true }); }); + it('logs an error if fetching datafile fails', async () => { + manager.queuedResponses.push( + { + statusCode: 500, + body: '', + headers: {}, + } + ); + + manager.start(); + await advanceTimersByTime(1000); + await manager.responsePromises[0]; + + verify(spiedLogger.error('Datafile fetch request failed with status: 500')).once(); + }); + describe('initial state', () => { it('returns null from get before becoming ready', () => { expect(manager.get()).toEqual(''); From 311e7e21261e951cc834b9121136040bd5e208e3 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 27 Feb 2024 02:38:30 +0600 Subject: [PATCH 061/200] [FSSDK-9570] add explicit entry point for node, browser and react_native (#905) --- package.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/package.json b/package.json index 6e0fb5af8..b560b606b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,20 @@ "default": "./dist/optimizely.browser.es.min.js" } }, + "./node": { + "types": "./dist/index.node.d.ts", + "default": "./dist/optimizely.node.min.js" + }, + "./browser": { + "types": "./dist/index.browser.d.ts", + "require": "./dist/optimizely.browser.min.js", + "import": "./dist/optimizely.browser.es.js", + "default": "./dist/optimizely.browser.es.min.js" + }, + "./react_native": { + "types": "./dist/index.react_native.d.ts", + "default": "./dist/optimizely.react_native.min.js" + }, "./lite": { "types": "./dist/index.lite.d.ts", "node": "./dist/optimizely.lite.min.js", From f4300a4b1355d2087e5e0bd7371df0dd67331036 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 29 Feb 2024 22:34:02 +0600 Subject: [PATCH 062/200] [FSSDK-9999] add release workflow (#906) --- .github/workflows/release.yml | 80 +++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..0821ac676 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,80 @@ +name: Publish SDK to NPM + +on: + release: + types: [published, edited] + workflow_dispatch: {} + +jobs: + publish: + name: Publish to NPM + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || !github.event.release.draft }} + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 16 + registry-url: "https://registry.npmjs.org/" + always-auth: "true" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + + - name: Install dependencies + run: npm install + + - id: latest-release + name: Export latest release git tag + run: | + echo "latest-release-tag=$(curl -qsSL \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + "${{ github.api_url }}/repos/${{ github.repository }}/releases/latest" \ + | jq -r .tag_name)" >> $GITHUB_OUTPUT + + - id: npm-tag + name: Determine NPM tag + run: | + VERSION=$(jq -r '.version' package.json) + LATEST_RELEASE_TAG="${{ steps.latest-release.outputs['latest-release-tag']}}" + + if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then + GITHUB_REF=${{ github.ref }} + RELEASE_TAG=${GITHUB_REF#refs/tags/} + else + RELEASE_TAG="${{ github.event.release.tag_name }}" + fi + + if [[ $RELEASE_TAG == $LATEST_RELEASE_TAG ]]; then + echo "npm-tag=latest" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-beta"* ]]; then + echo "npm-tag=beta" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-alpha"* ]]; then + echo "npm-tag=alpha" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-rc"* ]]; then + echo "npm-tag=rc" >> "$GITHUB_OUTPUT" + else + echo "npm-tag=v$(echo $VERSION | awk -F. '{print $1}')-latest" >> "$GITHUB_OUTPUT" + fi + + - id: release + name: Test, build and publish to npm + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + run: | + if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then + DRY_RUN="--dry-run" + fi + npm publish --tag=${{ steps.npm-tag.outputs['npm-tag'] }} $DRY_RUN + + - name: Report results to Jellyfish + uses: optimizely/jellyfish-deployment-reporter-action@main + if: ${{ always() && github.event_name == 'release' && (steps.release.outcome == 'success' || steps.release.outcome == 'failure') }} + with: + jellyfish_api_token: ${{ secrets.JELLYFISH_API_TOKEN }} + is_successful: ${{ steps.release.outcome == 'success' }} From b3a2dab5ff047c1422e7208f26ea9435bb2176bd Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 29 Feb 2024 23:03:53 +0600 Subject: [PATCH 063/200] [FSSDK-9999] prepare release 5.1.0 (#907) --- CHANGELOG.md | 8 +++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/optimizely/index.tests.js | 66 ++++++++++++------------ lib/optimizely/index.ts | 4 +- lib/plugins/odp_manager/index.browser.ts | 4 +- lib/plugins/odp_manager/index.node.ts | 4 +- lib/utils/enums/index.ts | 3 +- package-lock.json | 2 +- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 12 files changed, 54 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc1b79c7..288cb3ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.1.0] - February 29, 2024 + +### New Features +- Add explicit entry points for node, browser and react_native, allowing imports like `import optimizelySdk from '@optimizely/optimizely-sdk/node'`, `import optimizelySdk from '@optimizely/optimizely-sdk/browser'`, `import optimizelySdk from '@optimizely/optimizely-sdk/react_native'` ([#905](https://github.com/optimizely/javascript-sdk/pull/905)) + +### Changed +- Log an error in DatafileManager when datafile fetch fails ([#904](https://github.com/optimizely/javascript-sdk/pull/904)) + ## [5.0.1] - February 20, 2024 ### Bug fixes diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index c6905d593..7a10fe86c 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.1'); + assert.equal(optlyInstance.clientVersion, '5.1.0'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 3c7594e64..e7eb5f366 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.1'); + assert.equal(optlyInstance.clientVersion, '5.1.0'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index ef480cf8c..348bf9c66 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.0.1'); + assert.equal(optlyInstance.clientVersion, '5.1.0'); }); describe('event processor configuration', function() { diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index f7eb2bac2..39e6c3c6e 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -413,7 +413,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -484,7 +484,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -553,7 +553,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -645,7 +645,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -727,7 +727,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -796,7 +796,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1051,7 +1051,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1113,7 +1113,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1151,7 +1151,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1196,7 +1196,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1234,7 +1234,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1272,7 +1272,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1322,7 +1322,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1369,7 +1369,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1412,7 +1412,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1457,7 +1457,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1506,7 +1506,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1551,7 +1551,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1596,7 +1596,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1634,7 +1634,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1679,7 +1679,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2515,7 +2515,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2584,7 +2584,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2630,7 +2630,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2685,7 +2685,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2749,7 +2749,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -4781,7 +4781,7 @@ describe('lib/optimizely', function() { ], revision: '241', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, @@ -6314,7 +6314,7 @@ describe('lib/optimizely', function() { ], revision: '35', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, @@ -6538,7 +6538,7 @@ describe('lib/optimizely', function() { ], revision: '35', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, @@ -6732,7 +6732,7 @@ describe('lib/optimizely', function() { ], revision: '35', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, @@ -9459,7 +9459,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -9542,7 +9542,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -9624,7 +9624,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index edf3691a8..ef57822c2 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -64,7 +64,7 @@ import { DECISION_NOTIFICATION_TYPES, NOTIFICATION_TYPES, NODE_CLIENT_ENGINE, - NODE_CLIENT_VERSION, + CLIENT_VERSION, ODP_DEFAULT_EVENT_TYPE, FS_USER_ID_ALIAS, ODP_USER_KEY, @@ -106,7 +106,7 @@ export default class Optimizely implements Client { } this.clientEngine = clientEngine; - this.clientVersion = config.clientVersion || NODE_CLIENT_VERSION; + this.clientVersion = config.clientVersion || CLIENT_VERSION; this.errorHandler = config.errorHandler; this.isOptimizelyConfigValid = config.isValidInstance; this.logger = config.logger; diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index 989774e5a..171b93566 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -15,7 +15,7 @@ */ import { - BROWSER_CLIENT_VERSION, + CLIENT_VERSION, ERROR_MESSAGES, JAVASCRIPT_CLIENT_ENGINE, ODP_USER_KEY, @@ -64,7 +64,7 @@ export class BrowserOdpManager extends OdpManager { } const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; - const browserClientVersion = BROWSER_CLIENT_VERSION; + const browserClientVersion = CLIENT_VERSION; let customSegmentRequestHandler; diff --git a/lib/plugins/odp_manager/index.node.ts b/lib/plugins/odp_manager/index.node.ts index 7bd4d200f..4f01ededc 100644 --- a/lib/plugins/odp_manager/index.node.ts +++ b/lib/plugins/odp_manager/index.node.ts @@ -22,7 +22,7 @@ import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { LOG_MESSAGES, NODE_CLIENT_ENGINE, - NODE_CLIENT_VERSION, + CLIENT_VERSION, REQUEST_TIMEOUT_ODP_EVENTS_MS, REQUEST_TIMEOUT_ODP_SEGMENTS_MS, } from '../../utils/enums'; @@ -57,7 +57,7 @@ export class NodeOdpManager extends OdpManager { } const nodeClientEngine = NODE_CLIENT_ENGINE; - const nodeClientVersion = NODE_CLIENT_VERSION; + const nodeClientVersion = CLIENT_VERSION; let customSegmentRequestHandler; diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 3456343e2..a38f5b0cd 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -223,8 +223,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const BROWSER_CLIENT_VERSION = '5.0.1'; -export const NODE_CLIENT_VERSION = '5.0.1'; +export const CLIENT_VERSION = '5.1.0'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 9d131ca29..000c375b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.1", + "version": "5.1.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b560b606b..ab355d056 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.0.1", + "version": "5.1.0", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index ae42147a2..bf9c89ee3 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.0.1'); + expect(optlyInstance.clientVersion).toEqual('5.1.0'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From ce1c43596f908df203421fe922da93904a1b2545 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 1 Mar 2024 17:23:44 +0600 Subject: [PATCH 064/200] [FSSDK-9999] remove jellyfish step from release workflow (#908) also update v5.1.0 release date in CHANGELOG.md --- .github/workflows/release.yml | 12 ++++++------ CHANGELOG.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0821ac676..4d0e71680 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,9 +72,9 @@ jobs: fi npm publish --tag=${{ steps.npm-tag.outputs['npm-tag'] }} $DRY_RUN - - name: Report results to Jellyfish - uses: optimizely/jellyfish-deployment-reporter-action@main - if: ${{ always() && github.event_name == 'release' && (steps.release.outcome == 'success' || steps.release.outcome == 'failure') }} - with: - jellyfish_api_token: ${{ secrets.JELLYFISH_API_TOKEN }} - is_successful: ${{ steps.release.outcome == 'success' }} + # - name: Report results to Jellyfish + # uses: optimizely/jellyfish-deployment-reporter-action@main + # if: ${{ always() && github.event_name == 'release' && (steps.release.outcome == 'success' || steps.release.outcome == 'failure') }} + # with: + # jellyfish_api_token: ${{ secrets.JELLYFISH_API_TOKEN }} + # is_successful: ${{ steps.release.outcome == 'success' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 288cb3ceb..2b8b4fbdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## [5.1.0] - February 29, 2024 +## [5.1.0] - March 1, 2024 ### New Features - Add explicit entry points for node, browser and react_native, allowing imports like `import optimizelySdk from '@optimizely/optimizely-sdk/node'`, `import optimizelySdk from '@optimizely/optimizely-sdk/browser'`, `import optimizelySdk from '@optimizely/optimizely-sdk/react_native'` ([#905](https://github.com/optimizely/javascript-sdk/pull/905)) From 4044bfb710fed99cda14cac975ad8e77bc232d88 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 1 Mar 2024 20:09:51 +0600 Subject: [PATCH 065/200] chore(deps-dev): bump ip from 1.1.8 to 1.1.9 (#910) Bumps [ip](https://github.com/indutny/node-ip) from 1.1.8 to 1.1.9. - [Commits](https://github.com/indutny/node-ip/compare/v1.1.8...v1.1.9) --- updated-dependencies: - dependency-name: ip dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 000c375b0..a24b5479c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.0.1", + "version": "5.1.0", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", @@ -8511,9 +8511,9 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true, "peer": true }, @@ -21528,9 +21528,9 @@ } }, "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true, "peer": true }, From 0788f85ed44425e4f01dd29eb38c2571a9317251 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 7 Mar 2024 23:22:15 +0600 Subject: [PATCH 066/200] [FSSDK-9586] update node versions (#911) --- .github/workflows/javascript.yml | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 33d5b6c28..bc0c6af62 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 cache-dependency-path: ./package-lock.json cache: 'npm' - name: Run linting @@ -50,7 +50,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 cache: 'npm' cache-dependency-path: ./package-lock.json - name: Cross-browser and umd unit tests @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ['14', '16', '18' ] + node: ['16', '18', '20'] steps: - uses: actions/checkout@v3 - name: Set up Node ${{ matrix.node }} diff --git a/package.json b/package.json index ab355d056..15bc83078 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ }, "license": "Apache-2.0", "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "keywords": [ "optimizely" From a1658d0498d8de7b71aacb0daa5bb061e8faeec5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 8 Mar 2024 01:54:59 +0600 Subject: [PATCH 067/200] [FSSDK-9586] restore node engine to >=14 (#912) also add back unit test ci for node14 --- .github/workflows/javascript.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index bc0c6af62..cf3f8c49f 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ['16', '18', '20'] + node: ['14', '16', '18', '20'] steps: - uses: actions/checkout@v3 - name: Set up Node ${{ matrix.node }} diff --git a/package.json b/package.json index 15bc83078..ab355d056 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ }, "license": "Apache-2.0", "engines": { - "node": ">=16.0.0" + "node": ">=14.0.0" }, "keywords": [ "optimizely" From 72830193ca7eb4c400a5225a2f453e6f01a1dbf9 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 12 Mar 2024 23:18:35 +0600 Subject: [PATCH 068/200] [FSSDK-9621] remove duplicate reactNativeAsyncStorageCache (#913) --- .../datafile-manager/datafileManager.ts | 4 +- .../httpPollingDatafileManager.ts | 12 ++-- .../persistentKeyValueCache.ts | 59 ------------------ .../reactNativeAsyncStorageCache.ts | 40 ------------- .../reactNativeDatafileManager.ts | 4 +- .../persistentKeyValueCache.ts | 60 ------------------- .../reactNativeAsyncStorageCache.ts | 47 --------------- .../event_processor/reactNativeEventsStore.ts | 19 +++--- .../browserAsyncStorageCache.ts | 10 ++-- .../persistentKeyValueCache.ts | 12 ++-- .../reactNativeAsyncStorageCache.ts | 6 +- tests/browserAsyncStorageCache.spec.ts | 6 +- tests/httpPollingDatafileManager.spec.ts | 10 ++-- tests/reactNativeAsyncStorageCache.spec.ts | 6 +- tests/vuidManager.spec.ts | 6 +- 15 files changed, 47 insertions(+), 254 deletions(-) delete mode 100644 lib/modules/datafile-manager/persistentKeyValueCache.ts delete mode 100644 lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts delete mode 100644 lib/modules/event_processor/persistentKeyValueCache.ts delete mode 100644 lib/modules/event_processor/reactNativeAsyncStorageCache.ts diff --git a/lib/modules/datafile-manager/datafileManager.ts b/lib/modules/datafile-manager/datafileManager.ts index 9e748d174..abf11d8e9 100644 --- a/lib/modules/datafile-manager/datafileManager.ts +++ b/lib/modules/datafile-manager/datafileManager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import PersistentKeyValueCache from './persistentKeyValueCache'; +import PersistentKeyValueCache from '../../plugins/key_value_cache/persistentKeyValueCache'; export interface DatafileUpdate { datafile: string; diff --git a/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/lib/modules/datafile-manager/httpPollingDatafileManager.ts index a1d1829e3..c3311997e 100644 --- a/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ b/lib/modules/datafile-manager/httpPollingDatafileManager.ts @@ -21,7 +21,7 @@ import EventEmitter, { Disposer } from './eventEmitter'; import { AbortableRequest, Response, Headers } from './http'; import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './config'; import BackoffController from './backoffController'; -import PersistentKeyValueCache from './persistentKeyValueCache'; +import PersistentKeyValueCache from '../../plugins/key_value_cache/persistentKeyValueCache'; import { NotificationRegistry } from './../../core/notification_center/notification_registry'; import { NOTIFICATION_TYPES } from '../../utils/enums'; @@ -35,8 +35,8 @@ function isSuccessStatusCode(statusCode: number): boolean { } const noOpKeyValueCache: PersistentKeyValueCache = { - get(): Promise<string> { - return Promise.resolve(''); + get(): Promise<string | undefined> { + return Promise.resolve(undefined); }, set(): Promise<void> { @@ -47,8 +47,8 @@ const noOpKeyValueCache: PersistentKeyValueCache = { return Promise.resolve(false); }, - remove(): Promise<void> { - return Promise.resolve(); + remove(): Promise<boolean> { + return Promise.resolve(false); }, }; @@ -339,7 +339,7 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana setDatafileFromCacheIfAvailable(): void { this.cache.get(this.cacheKey).then(datafile => { - if (this.isStarted && !this.isReadyPromiseSettled && datafile !== '') { + if (this.isStarted && !this.isReadyPromiseSettled && datafile) { logger.debug('Using datafile from cache'); this.currentDatafile = datafile; this.resolveReadyPromise(); diff --git a/lib/modules/datafile-manager/persistentKeyValueCache.ts b/lib/modules/datafile-manager/persistentKeyValueCache.ts deleted file mode 100644 index c7fdb2660..000000000 --- a/lib/modules/datafile-manager/persistentKeyValueCache.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys and values. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. string if value found was stored as a string. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<string>; - - /** - * Stores string in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: string): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts b/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 612dd1ec2..000000000 --- a/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<string> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return ''; - } - return val; - }); - } - - set(key: string, val: string): Promise<void> { - return AsyncStorage.setItem(key, val); - } - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/lib/modules/datafile-manager/reactNativeDatafileManager.ts b/lib/modules/datafile-manager/reactNativeDatafileManager.ts index 04b7eca4a..c3857c2fe 100644 --- a/lib/modules/datafile-manager/reactNativeDatafileManager.ts +++ b/lib/modules/datafile-manager/reactNativeDatafileManager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { makeGetRequest } from './browserRequest'; import HttpPollingDatafileManager from './httpPollingDatafileManager'; import { Headers, AbortableRequest } from './http'; import { DatafileManagerConfig } from './datafileManager'; -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache'; +import ReactNativeAsyncStorageCache from '../../plugins/key_value_cache/reactNativeAsyncStorageCache'; export default class ReactNativeDatafileManager extends HttpPollingDatafileManager { protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { diff --git a/lib/modules/event_processor/persistentKeyValueCache.ts b/lib/modules/event_processor/persistentKeyValueCache.ts deleted file mode 100644 index 7dd508df9..000000000 --- a/lib/modules/event_processor/persistentKeyValueCache.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys - * and JSON Object as value. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. Object if value found was stored as a JSON Object. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<any | null>; - - /** - * Stores Object in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: any): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/lib/modules/event_processor/reactNativeAsyncStorageCache.ts b/lib/modules/event_processor/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 26b2ac315..000000000 --- a/lib/modules/event_processor/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<any | null> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return null; - } - return JSON.parse(val); - }); - } - - /* eslint-disable */ - set(key: string, val: any): Promise<void> { - try { - return AsyncStorage.setItem(key, JSON.stringify(val)); - } catch (ex) { - return Promise.reject(ex); - } - } - /* eslint-enable */ - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/lib/modules/event_processor/reactNativeEventsStore.ts b/lib/modules/event_processor/reactNativeEventsStore.ts index d07928afa..a17bc3729 100644 --- a/lib/modules/event_processor/reactNativeEventsStore.ts +++ b/lib/modules/event_processor/reactNativeEventsStore.ts @@ -1,6 +1,6 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { getLogger } from '../logging' import { objectValues } from "../../utils/fns" import { Synchronizer } from './synchronizer' -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache' +import ReactNativeAsyncStorageCache from '../../plugins/key_value_cache/reactNativeAsyncStorageCache'; const logger = getLogger('ReactNativeEventsStore') @@ -38,10 +38,10 @@ export class ReactNativeEventsStore<T> { public async set(key: string, event: T): Promise<string> { await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} + const eventsMap: {[key: string]: T} = await this.getEventsMap(); if (Object.keys(eventsMap).length < this.maxSize) { eventsMap[key] = event - await this.cache.set(this.storeKey, eventsMap) + await this.cache.set(this.storeKey, JSON.stringify(eventsMap)) } else { logger.warn('React native events store is full. Store key: %s', this.storeKey) } @@ -51,27 +51,28 @@ export class ReactNativeEventsStore<T> { public async get(key: string): Promise<T> { await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} + const eventsMap: {[key: string]: T} = await this.getEventsMap() this.synchronizer.releaseLock() return eventsMap[key] } public async getEventsMap(): Promise<{[key: string]: T}> { - return await this.cache.get(this.storeKey) || {} + const cachedValue = await this.cache.get(this.storeKey) || '{}'; + return JSON.parse(cachedValue) } public async getEventsList(): Promise<T[]> { await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} + const eventsMap: {[key: string]: T} = await this.getEventsMap() this.synchronizer.releaseLock() return objectValues(eventsMap) } public async remove(key: string): Promise<void> { await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} + const eventsMap: {[key: string]: T} = await this.getEventsMap() eventsMap[key] && delete eventsMap[key] - await this.cache.set(this.storeKey, eventsMap) + await this.cache.set(this.storeKey, JSON.stringify(eventsMap)) this.synchronizer.releaseLock() } diff --git a/lib/plugins/key_value_cache/browserAsyncStorageCache.ts b/lib/plugins/key_value_cache/browserAsyncStorageCache.ts index 48d64ca70..508a9e5f4 100644 --- a/lib/plugins/key_value_cache/browserAsyncStorageCache.ts +++ b/lib/plugins/key_value_cache/browserAsyncStorageCache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,14 +34,14 @@ export default class BrowserAsyncStorageCache implements PersistentKeyValueCache }); } - async get(key: string): Promise<string | null | undefined> { - return tryWithLocalStorage<string | null | undefined>({ + async get(key: string): Promise<string | undefined> { + return tryWithLocalStorage<string | undefined>({ browserCallback: (localStorage?: Storage) => { - return localStorage?.getItem(key); + return (localStorage?.getItem(key) || undefined); }, nonBrowserCallback: () => { this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); - return null; + return undefined; }, }); } diff --git a/lib/plugins/key_value_cache/persistentKeyValueCache.ts b/lib/plugins/key_value_cache/persistentKeyValueCache.ts index 268456d9f..f6b182738 100644 --- a/lib/plugins/key_value_cache/persistentKeyValueCache.ts +++ b/lib/plugins/key_value_cache/persistentKeyValueCache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ */ /** - * An Interface to implement a persistent key value cache which supports strings as keys and value objects. + * An Interface to implement a persistent key value cache which supports strings as keys. */ -export default interface PersistentKeyValueCache { +export default interface PersistentKeyValueCache<V = string> { /** * Checks if a key exists in the cache * @param key @@ -37,8 +37,7 @@ export default interface PersistentKeyValueCache { * 2. undefined if the key does not exist in the cache. * Rejects the promise in case of an error */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(key: string): Promise<any>; + get(key: string): Promise<V | undefined>; /** * Removes the key value pair from cache. @@ -59,6 +58,5 @@ export default interface PersistentKeyValueCache { * Resolves promise without a value if successful * Rejects the promise in case of an error */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - set(key: string, val: any): Promise<void>; + set(key: string, val: V): Promise<void>; } diff --git a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts index 3aced8bdc..80930cfb6 100644 --- a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts +++ b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020, 2022, Optimizely + * Copyright 2020, 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ export default class ReactNativeAsyncStorageCache implements PersistentKeyValueC return await AsyncStorage.getItem(key) !== null; } - async get(key: string): Promise<string | null> { - return await AsyncStorage.getItem(key); + async get(key: string): Promise<string | undefined> { + return (await AsyncStorage.getItem(key) || undefined); } async remove(key: string): Promise<boolean> { diff --git a/tests/browserAsyncStorageCache.spec.ts b/tests/browserAsyncStorageCache.spec.ts index 0e690443a..ca5417b09 100644 --- a/tests/browserAsyncStorageCache.spec.ts +++ b/tests/browserAsyncStorageCache.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022,2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,10 +63,10 @@ describe('BrowserAsyncStorageCache', () => { expect(foundValue).toEqual(VALUE_FOR_KEY_THAT_EXISTS); }); - it('should return empty string if item is not found in cache', async () => { + it('should return undefined if item is not found in cache', async () => { const json = await cacheInstance.get(NONEXISTENT_KEY); - expect(json).toBeNull(); + expect(json).toBeUndefined(); }); }); diff --git a/tests/httpPollingDatafileManager.spec.ts b/tests/httpPollingDatafileManager.spec.ts index 94439559d..64f7317ac 100644 --- a/tests/httpPollingDatafileManager.spec.ts +++ b/tests/httpPollingDatafileManager.spec.ts @@ -18,7 +18,7 @@ import HttpPollingDatafileManager from '../lib/modules/datafile-manager/httpPoll import { Headers, AbortableRequest, Response } from '../lib/modules/datafile-manager/http'; import { DatafileManagerConfig } from '../lib/modules/datafile-manager/datafileManager'; import { advanceTimersByTime, getTimerCount } from './testUtils'; -import PersistentKeyValueCache from '../lib/modules/datafile-manager/persistentKeyValueCache'; +import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; jest.mock('../lib/modules/datafile-manager/backoffController', () => { @@ -72,8 +72,8 @@ export class TestDatafileManager extends HttpPollingDatafileManager { } const testCache: PersistentKeyValueCache = { - get(key: string): Promise<string> { - let val = ''; + get(key: string): Promise<string | undefined> { + let val = undefined; switch (key) { case 'opt-datafile-keyThatExists': val = JSON.stringify({ name: 'keyThatExists' }); @@ -90,8 +90,8 @@ const testCache: PersistentKeyValueCache = { return Promise.resolve(false); }, - remove(): Promise<void> { - return Promise.resolve(); + remove(): Promise<boolean> { + return Promise.resolve(false); }, }; diff --git a/tests/reactNativeAsyncStorageCache.spec.ts b/tests/reactNativeAsyncStorageCache.spec.ts index a7af194c4..2a26635ac 100644 --- a/tests/reactNativeAsyncStorageCache.spec.ts +++ b/tests/reactNativeAsyncStorageCache.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020, 2022, Optimizely + * Copyright 2020, 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,10 +55,10 @@ describe('ReactNativeAsyncStorageCache', () => { expect(parsedObject).toEqual(testObject); }); - it('should return empty string if item is not found in cache', async () => { + it('should return undefined if item is not found in cache', async () => { const json = await cacheInstance.get('keyThatDoesNotExist'); - expect(json).toBeNull(); + expect(json).toBeUndefined(); }); }); diff --git a/tests/vuidManager.spec.ts b/tests/vuidManager.spec.ts index 12ee6cff9..87ee8f666 100644 --- a/tests/vuidManager.spec.ts +++ b/tests/vuidManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKe import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; describe('VuidManager', () => { - let mockCache: PersistentKeyValueCache; + let mockCache: PersistentKeyValueCache<string>; beforeAll(() => { mockCache = mock<PersistentKeyValueCache>(); @@ -81,7 +81,7 @@ describe('VuidManager', () => { }); it('should handle no valid optimizely-vuid in the cache', async () => { - when(mockCache.get(anyString())).thenResolve(null); + when(mockCache.get(anyString())).thenResolve(undefined); const manager = await VuidManager.instance(instance(mockCache)); // load() called initially From 05547df68365c8de7f326c838c439ca865891579 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 18 Mar 2024 21:33:01 +0600 Subject: [PATCH 069/200] [FSSDK-9621] add persistentCacheProvider option for instantiation (#914) --- lib/index.react_native.ts | 9 +- .../event_processor/reactNativeEventsStore.ts | 6 +- .../v1/v1EventProcessor.react_native.ts | 17 +- ...ct_native_http_polling_datafile_manager.ts | 71 +++---- lib/shared_types.ts | 6 +- package-lock.json | 26 ++- package.json | 2 +- tests/reactNativeDatafileManger.spec.ts | 176 ++++++++++++++++++ tests/reactNativeEventsStore.spec.ts | 164 ++++++++++++++-- ...ctNativeHttpPollingDatafileManager.spec.ts | 88 +++++++++ tests/reactNativeV1EventProcessor.spec.ts | 66 +++++++ tests/testUtils.ts | 32 ++++ 12 files changed, 600 insertions(+), 63 deletions(-) create mode 100644 tests/reactNativeDatafileManger.spec.ts create mode 100644 tests/reactNativeHttpPollingDatafileManager.spec.ts create mode 100644 tests/reactNativeV1EventProcessor.spec.ts diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 7340a03e4..6584b46fb 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -93,6 +93,7 @@ const createInstance = function(config: Config): Client | null { batchSize: eventBatchSize, maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, notificationCenter, + peristentCacheProvider: config.persistentCacheProvider, }; const eventProcessor = createEventProcessor(eventProcessorConfig); @@ -109,7 +110,13 @@ const createInstance = function(config: Config): Client | null { logger, errorHandler, datafileManager: config.sdkKey - ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) + ? createHttpPollingDatafileManager( + config.sdkKey, + logger, + config.datafile, + config.datafileOptions, + config.persistentCacheProvider, + ) : undefined, notificationCenter, isValidInstance: isValidInstance, diff --git a/lib/modules/event_processor/reactNativeEventsStore.ts b/lib/modules/event_processor/reactNativeEventsStore.ts index a17bc3729..b0ef113f3 100644 --- a/lib/modules/event_processor/reactNativeEventsStore.ts +++ b/lib/modules/event_processor/reactNativeEventsStore.ts @@ -19,6 +19,7 @@ import { objectValues } from "../../utils/fns" import { Synchronizer } from './synchronizer' import ReactNativeAsyncStorageCache from '../../plugins/key_value_cache/reactNativeAsyncStorageCache'; +import PersistentKeyValueCache from '../../plugins/key_value_cache/persistentKeyValueCache'; const logger = getLogger('ReactNativeEventsStore') @@ -29,11 +30,12 @@ export class ReactNativeEventsStore<T> { private maxSize: number private storeKey: string private synchronizer: Synchronizer = new Synchronizer() - private cache: ReactNativeAsyncStorageCache = new ReactNativeAsyncStorageCache() + private cache: PersistentKeyValueCache; - constructor(maxSize: number, storeKey: string) { + constructor(maxSize: number, storeKey: string, cache?: PersistentKeyValueCache) { this.maxSize = maxSize this.storeKey = storeKey + this.cache = cache || new ReactNativeAsyncStorageCache() } public async set(key: string, event: T): Promise<string> { diff --git a/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts b/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts index b0cc9b8b7..bd40a88bd 100644 --- a/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts +++ b/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ import { EventDispatcher, EventDispatcherResponse, } from '../eventDispatcher' +import { PersistentCacheProvider } from '../../../shared_types' const logger = getLogger('ReactNativeEventProcessor') @@ -91,12 +92,14 @@ export class LogTierV1EventProcessor implements EventProcessor { batchSize = DEFAULT_BATCH_SIZE, maxQueueSize = DEFAULT_MAX_QUEUE_SIZE, notificationCenter, + persistentCacheProvider, }: { dispatcher: EventDispatcher flushInterval?: number batchSize?: number maxQueueSize?: number notificationCenter?: NotificationSender + persistentCacheProvider?: PersistentCacheProvider }) { this.dispatcher = dispatcher this.notificationSender = notificationCenter @@ -105,8 +108,16 @@ export class LogTierV1EventProcessor implements EventProcessor { flushInterval = validateAndGetFlushInterval(flushInterval) batchSize = validateAndGetBatchSize(batchSize) this.queue = getQueue(batchSize, flushInterval, areEventContextsEqual, this.drainQueue.bind(this)) - this.pendingEventsStore = new ReactNativeEventsStore(maxQueueSize, PENDING_EVENTS_STORE_KEY) - this.eventBufferStore = new ReactNativeEventsStore(maxQueueSize, EVENT_BUFFER_STORE_KEY) + this.pendingEventsStore = new ReactNativeEventsStore( + maxQueueSize, + PENDING_EVENTS_STORE_KEY, + persistentCacheProvider && persistentCacheProvider(), + ); + this.eventBufferStore = new ReactNativeEventsStore( + maxQueueSize, + EVENT_BUFFER_STORE_KEY, + persistentCacheProvider && persistentCacheProvider(), + ) } private async connectionListener(state: NetInfoState) { diff --git a/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts b/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts index e5a4eba83..0d45d2116 100644 --- a/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts +++ b/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021-2022, Optimizely + * Copyright 2021-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,37 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { LoggerFacade } from '../../modules/logging'; - import { HttpPollingDatafileManager } from '../../modules/datafile-manager/index.react_native'; - import { DatafileOptions, DatafileManagerConfig, DatafileManager } from '../../shared_types'; - import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; - import fns from '../../utils/fns'; +import { LoggerFacade } from '../../modules/logging'; +import { HttpPollingDatafileManager } from '../../modules/datafile-manager/index.react_native'; +import { DatafileOptions, DatafileManager, PersistentCacheProvider } from '../../shared_types'; +import { DatafileManagerConfig } from '../../modules/datafile-manager/index.react_native'; +import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; +import fns from '../../utils/fns'; - export function createHttpPollingDatafileManager( - sdkKey: string, - logger: LoggerFacade, - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - datafileOptions?: DatafileOptions, +export function createHttpPollingDatafileManager( + sdkKey: string, + logger: LoggerFacade, + // TODO[OASIS-6649]: Don't use object type + // eslint-disable-next-line @typescript-eslint/ban-types + datafile?: string | object, + datafileOptions?: DatafileOptions, + persistentCacheProvider?: PersistentCacheProvider, ): DatafileManager { - const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; - if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { - fns.assign(datafileManagerConfig, datafileOptions); - } - if (datafile) { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: datafile, - jsonSchemaValidator: undefined, - logger: logger, - }); - - if (error) { - logger.error(error); - } - if (configObj) { - datafileManagerConfig.datafile = toDatafile(configObj); - } - } - return new HttpPollingDatafileManager(datafileManagerConfig); - } + const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; + if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { + fns.assign(datafileManagerConfig, datafileOptions); + } + if (datafile) { + const { configObj, error } = tryCreatingProjectConfig({ + datafile: datafile, + jsonSchemaValidator: undefined, + logger: logger, + }); + + if (error) { + logger.error(error); + } + if (configObj) { + datafileManagerConfig.datafile = toDatafile(configObj); + } + } + if (persistentCacheProvider) { + datafileManagerConfig.cache = persistentCacheProvider(); + } + return new HttpPollingDatafileManager(datafileManagerConfig); +} diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 2ae1ffb99..4af80fa13 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2023, Optimizely + * Copyright 2020-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import { IOdpEventApiManager } from './core/odp/odp_event_api_manager'; import { IOdpEventManager } from './core/odp/odp_event_manager'; import { IOdpManager } from './core/odp/odp_manager'; import { IUserAgentParser } from './core/odp/user_agent_parser'; +import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; export interface BucketerParams { experimentId: string; @@ -382,6 +383,8 @@ export interface TrackListenerPayload extends ListenerPayload { logEvent: Event; } +export type PersistentCacheProvider = () => PersistentCache; + /** * Entry level Config Entities * For compatibility with the previous declaration file @@ -393,6 +396,7 @@ export interface Config extends ConfigLite { eventMaxQueueSize?: number; // Maximum size for the event queue sdkKey?: string; odpOptions?: OdpOptions; + persistentCacheProvider?: PersistentCacheProvider; } /** diff --git a/package-lock.json b/package-lock.json index a24b5479c..5bfa3b904 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/chai": "^4.2.11", - "@types/jest": "^23.3.14", + "@types/jest": "^29.5.12", "@types/mocha": "^5.2.7", "@types/nise": "^1.4.0", "@types/node": "^18.7.18", @@ -4493,10 +4493,14 @@ } }, "node_modules/@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } }, "node_modules/@types/jsdom": { "version": "20.0.1", @@ -18497,10 +18501,14 @@ } }, "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } }, "@types/jsdom": { "version": "20.0.1", diff --git a/package.json b/package.json index ab355d056..4c76ff5e7 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/chai": "^4.2.11", - "@types/jest": "^23.3.14", + "@types/jest": "^29.5.12", "@types/mocha": "^5.2.7", "@types/nise": "^1.4.0", "@types/node": "^18.7.18", diff --git a/tests/reactNativeDatafileManger.spec.ts b/tests/reactNativeDatafileManger.spec.ts new file mode 100644 index 000000000..45a23f0b0 --- /dev/null +++ b/tests/reactNativeDatafileManger.spec.ts @@ -0,0 +1,176 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const mockGet = jest.fn().mockImplementation((key: string): Promise<string | undefined> => { + let val = undefined; + switch (key) { + case 'opt-datafile-keyThatExists': + val = JSON.stringify({ name: 'keyThatExists' }); + break; + } + return Promise.resolve(val); +}); + +const mockSet = jest.fn().mockImplementation((): Promise<void> => { + return Promise.resolve(); +}); + +const mockContains = jest.fn().mockImplementation((): Promise<boolean> => { + return Promise.resolve(false); +}); + +const mockRemove = jest.fn().mockImplementation((): Promise<boolean> => { + return Promise.resolve(false); +}); + +jest.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { + return jest.fn().mockImplementation(() => { + return { + get: mockGet, + set: mockSet, + contains: mockContains, + remove: mockRemove, + } + }); +}); + +import { advanceTimersByTime } from './testUtils'; +import ReactNativeDatafileManager from '../lib/modules/datafile-manager/reactNativeDatafileManager'; +import { Headers, AbortableRequest, Response } from '../lib/modules/datafile-manager/http'; +import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; +import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; + +class MockRequestReactNativeDatafileManager extends ReactNativeDatafileManager { + queuedResponses: (Response | Error)[] = []; + + responsePromises: Promise<Response>[] = []; + + simulateResponseDelay = false; + + makeGetRequest(url: string, headers: Headers): AbortableRequest { + const nextResponse: Error | Response | undefined = this.queuedResponses.pop(); + let responsePromise: Promise<Response>; + if (nextResponse === undefined) { + responsePromise = Promise.reject('No responses queued'); + } else if (nextResponse instanceof Error) { + responsePromise = Promise.reject(nextResponse); + } else { + if (this.simulateResponseDelay) { + // Actual response will have some delay. This is required to get expected behavior for caching. + responsePromise = new Promise(resolve => setTimeout(() => resolve(nextResponse), 50)); + } else { + responsePromise = Promise.resolve(nextResponse); + } + } + this.responsePromises.push(responsePromise); + return { responsePromise, abort: jest.fn() }; + } +} + +describe('reactNativeDatafileManager', () => { + const MockedReactNativeAsyncStorageCache = jest.mocked(ReactNativeAsyncStorageCache); + + const testCache: PersistentKeyValueCache = { + get(key: string): Promise<string | undefined> { + let val = undefined; + switch (key) { + case 'opt-datafile-keyThatExists': + val = JSON.stringify({ name: 'keyThatExists' }); + break; + } + return Promise.resolve(val); + }, + + set(): Promise<void> { + return Promise.resolve(); + }, + + contains(): Promise<boolean> { + return Promise.resolve(false); + }, + + remove(): Promise<boolean> { + return Promise.resolve(false); + }, + }; + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + jest.clearAllTimers(); + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockSet.mockClear(); + mockContains.mockClear(); + mockRemove.mockClear(); + }); + + it('uses the user provided cache', async () => { + const cache = { + get: mockGet, + set: mockSet, + contains: mockContains, + remove: mockRemove, + }; + + const manager = new MockRequestReactNativeDatafileManager({ + sdkKey: 'keyThatExists', + updateInterval: 500, + autoUpdate: true, + cache, + }); + + manager.simulateResponseDelay = true; + + manager.queuedResponses.push({ + statusCode: 200, + body: '{"foo": "bar"}', + headers: {}, + }); + manager.start(); + await manager.onReady(); + await advanceTimersByTime(50); + expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); + expect(mockSet.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); + expect(JSON.parse(mockSet.mock.calls[0][1])).toEqual({ foo: 'bar' }); + }); + + it('uses ReactNativeAsyncStorageCache if no cache is provided', async () => { + const manager = new MockRequestReactNativeDatafileManager({ + sdkKey: 'keyThatExists', + updateInterval: 500, + autoUpdate: true + }); + manager.simulateResponseDelay = true; + + manager.queuedResponses.push({ + statusCode: 200, + body: '{"foo": "bar"}', + headers: {}, + }); + + manager.start(); + await manager.onReady(); + await advanceTimersByTime(50); + + expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); + expect(mockSet.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); + expect(JSON.parse(mockSet.mock.calls[0][1] as string)).toEqual({ foo: 'bar' }); + }); +}); diff --git a/tests/reactNativeEventsStore.spec.ts b/tests/reactNativeEventsStore.spec.ts index 165a9c798..cba3185ef 100644 --- a/tests/reactNativeEventsStore.spec.ts +++ b/tests/reactNativeEventsStore.spec.ts @@ -15,25 +15,109 @@ */ /// <reference types="jest" /> +const mockMap = new Map(); + +const mockGet = jest.fn().mockImplementation((key) => { + return Promise.resolve(mockMap.get(key)); +}); + +const mockSet = jest.fn().mockImplementation((key, value) => { + mockMap.set(key, value); + return Promise.resolve(); +}); + +const mockRemove = jest.fn().mockImplementation((key) => { + if (mockMap.has(key)) { + mockMap.delete(key); + return Promise.resolve(true); + } + return Promise.resolve(false); +}); + +const mockContains = jest.fn().mockImplementation((key) => { + return Promise.resolve(mockMap.has(key)); +}); + + +jest.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { + return jest.fn().mockImplementation(() => { + return { + get: mockGet, + contains: mockContains, + set: mockSet, + remove: mockRemove, + } + }); +}); + +import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; + import { ReactNativeEventsStore } from '../lib/modules/event_processor/reactNativeEventsStore' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' +import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache' const STORE_KEY = 'test-store' describe('ReactNativeEventsStore', () => { + const MockedReactNativeAsyncStorageCache = jest.mocked(ReactNativeAsyncStorageCache); let store: ReactNativeEventsStore<any> beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); store = new ReactNativeEventsStore(5, STORE_KEY) }) + describe('constructor', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + + it('uses the user provided cache', () => { + const cache = { + get: jest.fn(), + contains: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + } as jest.Mocked<PersistentKeyValueCache> + + const store = new ReactNativeEventsStore(5, STORE_KEY, cache); + store.clear(); + expect(cache.remove).toHaveBeenCalled(); + }); + + it('uses ReactNativeAsyncStorageCache if no cache is provided', () => { + const store = new ReactNativeEventsStore(5, STORE_KEY); + store.clear(); + expect(MockedReactNativeAsyncStorageCache).toHaveBeenCalledTimes(1); + expect(mockRemove).toHaveBeenCalled(); + }); + }); + describe('set', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + it('should store all the events correctly in the store', async () => { await store.set('event1', {'name': 'event1'}) await store.set('event2', {'name': 'event2'}) await store.set('event3', {'name': 'event3'}) await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + const storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, @@ -47,7 +131,7 @@ describe('ReactNativeEventsStore', () => { await store.set('event2', {'name': 'event2'}) await store.set('event3', {'name': 'event3'}) await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + const storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, @@ -58,6 +142,15 @@ describe('ReactNativeEventsStore', () => { }) describe('get', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + it('should correctly get items', async () => { await store.set('event1', {'name': 'event1'}) await store.set('event2', {'name': 'event2'}) @@ -71,6 +164,15 @@ describe('ReactNativeEventsStore', () => { }) describe('getEventsMap', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + it('should get the whole map correctly', async () => { await store.set('event1', {'name': 'event1'}) await store.set('event2', {'name': 'event2'}) @@ -87,6 +189,15 @@ describe('ReactNativeEventsStore', () => { }) describe('getEventsList', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + it('should get all the events as a list', async () => { await store.set('event1', {'name': 'event1'}) await store.set('event2', {'name': 'event2'}) @@ -103,12 +214,21 @@ describe('ReactNativeEventsStore', () => { }) describe('remove', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + it('should correctly remove items from the store', async () => { await store.set('event1', {'name': 'event1'}) await store.set('event2', {'name': 'event2'}) await store.set('event3', {'name': 'event3'}) await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, @@ -117,7 +237,7 @@ describe('ReactNativeEventsStore', () => { }) await store.remove('event1') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event2": { "name": "event2" }, "event3": { "name": "event3" }, @@ -125,7 +245,7 @@ describe('ReactNativeEventsStore', () => { }) await store.remove('event2') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event3": { "name": "event3" }, "event4": { "name": "event4" }, @@ -137,7 +257,7 @@ describe('ReactNativeEventsStore', () => { await store.set('event2', {'name': 'event2'}) await store.set('event3', {'name': 'event3'}) await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, @@ -149,18 +269,27 @@ describe('ReactNativeEventsStore', () => { await store.remove('event1') await store.remove('event2') await store.remove('event3') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event4": { "name": "event4" }}) }) }) describe('clear', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + it('should clear the whole store',async () => { await store.set('event1', {'name': 'event1'}) await store.set('event2', {'name': 'event2'}) await store.set('event3', {'name': 'event3'}) await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, @@ -168,19 +297,28 @@ describe('ReactNativeEventsStore', () => { "event4": { "name": "event4" }, }) await store.clear() - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY] || '{}') + storedPendingEvents = storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY) || '{}'); expect(storedPendingEvents).toEqual({}) }) }) describe('maxSize', () => { + beforeEach(() => { + MockedReactNativeAsyncStorageCache.mockClear(); + mockGet.mockClear(); + mockContains.mockClear(); + mockSet.mockClear(); + mockRemove.mockClear(); + mockMap.clear(); + }); + it('should not add anymore events if the store if full', async () => { await store.set('event1', {'name': 'event1'}) await store.set('event2', {'name': 'event2'}) await store.set('event3', {'name': 'event3'}) await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, @@ -189,7 +327,7 @@ describe('ReactNativeEventsStore', () => { }) await store.set('event5', {'name': 'event5'}) - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, @@ -199,7 +337,7 @@ describe('ReactNativeEventsStore', () => { }) await store.set('event6', {'name': 'event6'}) - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) + storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); expect(storedPendingEvents).toEqual({ "event1": { "name": "event1" }, "event2": { "name": "event2" }, diff --git a/tests/reactNativeHttpPollingDatafileManager.spec.ts b/tests/reactNativeHttpPollingDatafileManager.spec.ts new file mode 100644 index 000000000..d9da58604 --- /dev/null +++ b/tests/reactNativeHttpPollingDatafileManager.spec.ts @@ -0,0 +1,88 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +jest.mock('../lib/modules/datafile-manager/index.react_native', () => { + return { + HttpPollingDatafileManager: jest.fn().mockImplementation(() => { + return { + get(): string { + return '{}'; + }, + on(): (() => void) { + return () => {}; + }, + onReady(): Promise<void> { + return Promise.resolve(); + }, + }; + }), + } +}); + +import { HttpPollingDatafileManager } from '../lib/modules/datafile-manager/index.react_native'; +import { createHttpPollingDatafileManager } from '../lib/plugins/datafile_manager/react_native_http_polling_datafile_manager'; +import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; +import { PersistentCacheProvider } from '../lib/shared_types'; + +describe('createHttpPollingDatafileManager', () => { + const MockedHttpPollingDatafileManager = jest.mocked(HttpPollingDatafileManager); + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + jest.clearAllTimers(); + MockedHttpPollingDatafileManager.mockClear(); + }); + + it('calls the provided persistentCacheFactory and passes it to the HttpPollingDatafileManagerConstructor', async () => { + const fakePersistentCache : PersistentKeyValueCache = { + contains(k: string): Promise<boolean> { + return Promise.resolve(false); + }, + get(key: string): Promise<string | undefined> { + return Promise.resolve(undefined); + }, + remove(key: string): Promise<boolean> { + return Promise.resolve(false); + }, + set(key: string, val: string): Promise<void> { + return Promise.resolve() + } + } + + const fakePersistentCacheProvider = jest.fn().mockImplementation(() => { + return fakePersistentCache; + }) as jest.Mocked<PersistentCacheProvider>; + + const noop = () => {}; + + createHttpPollingDatafileManager( + 'test-key', + { log: noop, info: noop, debug: noop, error: noop, warn: noop }, + undefined, + {}, + fakePersistentCacheProvider, + ) + + expect(MockedHttpPollingDatafileManager).toHaveBeenCalledTimes(1); + + const { cache } = MockedHttpPollingDatafileManager.mock.calls[0][0]; + expect(cache === fakePersistentCache).toBeTruthy(); + }); +}); diff --git a/tests/reactNativeV1EventProcessor.spec.ts b/tests/reactNativeV1EventProcessor.spec.ts new file mode 100644 index 000000000..b296e6b0d --- /dev/null +++ b/tests/reactNativeV1EventProcessor.spec.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +jest.mock('../lib/modules/event_processor/reactNativeEventsStore'); + +import { ReactNativeEventsStore } from '../lib/modules/event_processor/reactNativeEventsStore'; +import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; +import { LogTierV1EventProcessor } from '../lib/modules/event_processor/index.react_native'; +import { PersistentCacheProvider } from '../lib/shared_types'; + +describe('LogTierV1EventProcessor', () => { + const MockedReactNativeEventsStore = jest.mocked(ReactNativeEventsStore); + + beforeEach(() => { + MockedReactNativeEventsStore.mockClear(); + }); + + it('calls the provided persistentCacheFactory and passes it to the ReactNativeEventStore constructor twice', async () => { + const getFakePersistentCache = () : PersistentKeyValueCache => { + return { + contains(k: string): Promise<boolean> { + return Promise.resolve(false); + }, + get(key: string): Promise<string | undefined> { + return Promise.resolve(undefined); + }, + remove(key: string): Promise<boolean> { + return Promise.resolve(false); + }, + set(key: string, val: string): Promise<void> { + return Promise.resolve() + } + }; + } + + let call = 0; + const fakeCaches = [getFakePersistentCache(), getFakePersistentCache()]; + const fakePersistentCacheProvider = jest.fn().mockImplementation(() => { + return fakeCaches[call++]; + }) as jest.Mocked<PersistentCacheProvider>; + + const noop = () => {}; + + new LogTierV1EventProcessor({ + dispatcher: { dispatchEvent: () => {} }, + persistentCacheProvider: fakePersistentCacheProvider, + }) + + expect(fakePersistentCacheProvider).toHaveBeenCalledTimes(2); + expect(MockedReactNativeEventsStore.mock.calls[0][2] === fakeCaches[0]).toBeTruthy(); + expect(MockedReactNativeEventsStore.mock.calls[1][2] === fakeCaches[1]).toBeTruthy(); + }); +}); diff --git a/tests/testUtils.ts b/tests/testUtils.ts index c828089fc..2c28c259b 100644 --- a/tests/testUtils.ts +++ b/tests/testUtils.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import PersistentKeyValueCache from "../lib/plugins/key_value_cache/persistentKeyValueCache"; + export function advanceTimersByTime(waitMs: number): Promise<void> { const timeoutPromise: Promise<void> = new Promise(res => setTimeout(res, waitMs)); jest.advanceTimersByTime(waitMs); @@ -25,3 +27,33 @@ export function getTimerCount(): number { // https://jestjs.io/docs/en/jest-object#jestgettimercount return (jest as any).getTimerCount(); } + + +export const getTestPersistentCache = (): PersistentKeyValueCache => { + const cache = { + get: jest.fn().mockImplementation((key: string): Promise<string | undefined> => { + let val = undefined; + switch (key) { + case 'opt-datafile-keyThatExists': + val = JSON.stringify({ name: 'keyThatExists' }); + break; + } + return Promise.resolve(val); + }), + + set: jest.fn().mockImplementation((): Promise<void> => { + console.log('mock set called'); + return Promise.resolve(); + }), + + contains: jest.fn().mockImplementation((): Promise<boolean> => { + return Promise.resolve(false); + }), + + remove: jest.fn().mockImplementation((): Promise<boolean> => { + return Promise.resolve(false); + }), + } as jest.Mocked<PersistentKeyValueCache>; + + return cache; +} From 6be2631c3940bda0120532279eea56c9fcccd37d Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 18 Mar 2024 22:07:49 +0600 Subject: [PATCH 070/200] [FSSDK-9621] prepare release 5.2.0 (#915) --- CHANGELOG.md | 5 +++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b8b4fbdc..3783c7650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.2.0] - March 18, 2024 + +### New Features +- Add `persistentCacheProvider` option to `createInstance` to allow providing custom persistent cache implementation in react native ([#914](https://github.com/optimizely/javascript-sdk/pull/914)) + ## [5.1.0] - March 1, 2024 ### New Features diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 7a10fe86c..8fb71ed68 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.1.0'); + assert.equal(optlyInstance.clientVersion, '5.2.0'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index e7eb5f366..167e8aeb5 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.1.0'); + assert.equal(optlyInstance.clientVersion, '5.2.0'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 348bf9c66..324711065 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.1.0'); + assert.equal(optlyInstance.clientVersion, '5.2.0'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index a38f5b0cd..4b5679960 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -223,7 +223,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.1.0'; +export const CLIENT_VERSION = '5.2.0'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 5bfa3b904..ed5b89a0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.1.0", + "version": "5.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.1.0", + "version": "5.2.0", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index 4c76ff5e7..40d703b3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.1.0", + "version": "5.2.0", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index bf9c89ee3..0a68676ae 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.1.0'); + expect(optlyInstance.clientVersion).toEqual('5.2.0'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From a1e6110cc48acd661f433c4072edf48274e13130 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Fri, 22 Mar 2024 05:12:56 -0400 Subject: [PATCH 071/200] [FSSDK-9984] fix: Empty fetched qualified segments validity (#916) * fix: empty segments array is valid; null segments is invalid * test: add tests around segment valid returns --------- Co-authored-by: Mike Chu <michael.chu@optmizely.com> --- lib/optimizely_user_context/index.tests.js | 34 ++++++++++++++++++++-- lib/optimizely_user_context/index.ts | 2 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 1b0d349ce..2b6ce0653 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -991,7 +991,7 @@ describe('lib/optimizely_user_context', function() { }); describe('fetchQualifiedSegments', () => { - it('should successfully call fetch qualified segments', async () => { + it('should successfully get segments', async () => { fakeOptimizely = { fetchQualifiedSegments: sinon.stub().returns(['a']), }; @@ -1001,13 +1001,41 @@ describe('lib/optimizely_user_context', function() { userId, }); - const fetchedQualifiedSegments = await user.fetchQualifiedSegments(); - assert.deepEqual(fetchedQualifiedSegments, true); + const successfullyFetched = await user.fetchQualifiedSegments(); + assert.deepEqual(successfullyFetched, true); sinon.assert.calledWithExactly(fakeOptimizely.fetchQualifiedSegments, userId, undefined); assert.deepEqual(user.qualifiedSegments, ['a']); }); + + it('should return true empty returned segements', async () => { + fakeOptimizely = { + fetchQualifiedSegments: sinon.stub().returns([]), + }; + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + assert.deepEqual(successfullyFetched, true); + }); + + it('should return false in other cases', async () => { + fakeOptimizely = { + fetchQualifiedSegments: sinon.stub().returns(null), + }; + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + assert.deepEqual(successfullyFetched, false); + }); }); describe('isQualifiedFor', () => { diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index 96f6bc3c2..de3930290 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -258,7 +258,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { this.qualifiedSegments = segments; - return !!segments; + return segments !== null; } /** From cf941c19afe71775d6f64acd02725bc353083c77 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:33:34 -0400 Subject: [PATCH 072/200] fix: npm audit fix for follow-redirects & get-func-name (#918) Co-authored-by: Mike Chu <michael.chu@optmizely.com> --- package-lock.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index ed5b89a0b..c3271cc8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7864,9 +7864,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -8036,9 +8036,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" @@ -21079,9 +21079,9 @@ "peer": true }, "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true }, "foreground-child": { @@ -21193,9 +21193,9 @@ "dev": true }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, "get-intrinsic": { From 53a2042dab94f6b35f3ad627b375c15650d9e537 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:06:28 -0400 Subject: [PATCH 073/200] [FSSDK-9984] chore: Prep release (#919) * chore: bump release version number * docs: update changelog --------- Co-authored-by: Mike Chu <michael.chu@optmizely.com> --- CHANGELOG.md | 6 ++++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3783c7650..3e669ca75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [5.2.1] - March 25, 2024 + +### Bug fixes +- Fix: empty segments collection is valid ([#916](https://github.com/optimizely/javascript-sdk/pull/916)) +- Update vulnerable dependencies ([#918](https://github.com/optimizely/javascript-sdk/pull/918)) + ## [5.2.0] - March 18, 2024 ### New Features diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 8fb71ed68..04e4a1559 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.2.0'); + assert.equal(optlyInstance.clientVersion, '5.2.1'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 167e8aeb5..44da77f11 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.2.0'); + assert.equal(optlyInstance.clientVersion, '5.2.1'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 324711065..0ceed3057 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.2.0'); + assert.equal(optlyInstance.clientVersion, '5.2.1'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 4b5679960..95fe4f4ed 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -223,7 +223,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.2.0'; +export const CLIENT_VERSION = '5.2.1'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index c3271cc8f..0e90154c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.2.0", + "version": "5.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.2.0", + "version": "5.2.1", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index 40d703b3f..cd15db28b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.2.0", + "version": "5.2.1", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 0a68676ae..afc59cc56 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.2.0'); + expect(optlyInstance.clientVersion).toEqual('5.2.1'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 75a01486272acfdc396cc0f159258a8066b6af99 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Sat, 6 Apr 2024 03:44:07 +0600 Subject: [PATCH 074/200] [FSSDK-10090] Refactor ODP integration (#920) * refactor * refactor: build working * fix segment manager tests * fix identify user * update event api manager * update event man * add sement manager test * fix odp event manager tests * odp manager tests * fix odp event manager tests * odpManager tests * odp manager tests * browserOdpManager tests fixed * fix project config tests * fix tests * fix * config manager tests fix * odpManager test * event man update * copyright * remove console log * fix review * undo clear timeout * remove unnecessary line --- lib/core/odp/odp_config.ts | 112 +-- lib/core/odp/odp_event_api_manager.ts | 30 +- lib/core/odp/odp_event_manager.ts | 129 ++-- lib/core/odp/odp_manager.ts | 216 ++++-- lib/core/odp/odp_segment_manager.ts | 22 +- lib/core/project_config/index.tests.js | 51 +- lib/core/project_config/index.ts | 62 +- .../project_config_manager.tests.js | 175 +++-- .../project_config/project_config_manager.ts | 8 +- lib/index.browser.tests.js | 26 +- lib/index.browser.ts | 5 +- lib/index.node.ts | 7 +- lib/index.react_native.ts | 5 +- lib/optimizely/index.tests.js | 25 +- lib/optimizely/index.ts | 83 +-- lib/optimizely_user_context/index.ts | 8 +- .../odp/event_api_manager/index.browser.ts | 40 +- .../odp/event_api_manager/index.node.ts | 29 +- lib/plugins/odp_manager/index.browser.ts | 106 +-- lib/plugins/odp_manager/index.node.ts | 85 ++- lib/shared_types.ts | 2 + lib/utils/enums/index.ts | 4 +- lib/utils/promise/resolvablePromise.ts | 34 + tests/odpEventApiManager.spec.ts | 33 +- tests/odpEventManager.spec.ts | 401 +++++++---- tests/odpManager.browser.spec.ts | 237 ++---- tests/odpManager.spec.ts | 675 +++++++++++++++--- tests/odpSegmentManager.spec.ts | 88 ++- tests/testUtils.ts | 7 +- 29 files changed, 1624 insertions(+), 1081 deletions(-) create mode 100644 lib/utils/promise/resolvablePromise.ts diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index 8593dbd2d..4e4f41855 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,90 +19,29 @@ import { checkArrayEquality } from '../../utils/fns'; export class OdpConfig { /** * Host of ODP audience segments API. - * @private */ - private _apiHost: string; - - /** - * Getter to retrieve the ODP server host - * @public - */ - get apiHost(): string { - return this._apiHost; - } + readonly apiHost: string; /** * Public API key for the ODP account from which the audience segments will be fetched (optional). - * @private */ - private _apiKey: string; - - /** - * Getter to retrieve the ODP API key - * @public - */ - get apiKey(): string { - return this._apiKey; - } + readonly apiKey: string; /** * Url for sending events via pixel. - * @private - */ - private _pixelUrl: string; - - /** - * Getter to retrieve the ODP pixel URL - * @public */ - get pixelUrl(): string { - return this._pixelUrl; - } + readonly pixelUrl: string; /** * All ODP segments used in the current datafile (associated with apiHost/apiKey). - * @private - */ - private _segmentsToCheck: string[]; - - /** - * Getter for ODP segments to check - * @public - */ - get segmentsToCheck(): string[] { - return this._segmentsToCheck; - } - - constructor(apiKey?: string, apiHost?: string, pixelUrl?: string, segmentsToCheck?: string[]) { - this._apiKey = apiKey ?? ''; - this._apiHost = apiHost ?? ''; - this._pixelUrl = pixelUrl ?? ''; - this._segmentsToCheck = segmentsToCheck ?? []; - } - - /** - * Update the ODP configuration details - * @param {OdpConfig} config New ODP Config to potentially update self with - * @returns true if configuration was updated successfully */ - update(config: OdpConfig): boolean { - if (this.equals(config)) { - return false; - } else { - if (config.apiKey) this._apiKey = config.apiKey; - if (config.apiHost) this._apiHost = config.apiHost; - if (config.pixelUrl) this._pixelUrl = config.pixelUrl; - if (config.segmentsToCheck) this._segmentsToCheck = config.segmentsToCheck; + readonly segmentsToCheck: string[]; - return true; - } - } - - /** - * Determines if ODP configuration has the minimum amount of information - */ - isReady(): boolean { - return !!this._apiKey && !!this._apiHost; + constructor(apiKey: string, apiHost: string, pixelUrl: string, segmentsToCheck: string[]) { + this.apiKey = apiKey; + this.apiHost = apiHost; + this.pixelUrl = pixelUrl; + this.segmentsToCheck = segmentsToCheck; } /** @@ -112,10 +51,33 @@ export class OdpConfig { */ equals(configToCompare: OdpConfig): boolean { return ( - this._apiHost === configToCompare._apiHost && - this._apiKey === configToCompare._apiKey && - this._pixelUrl === configToCompare._pixelUrl && - checkArrayEquality(this.segmentsToCheck, configToCompare._segmentsToCheck) + this.apiHost === configToCompare.apiHost && + this.apiKey === configToCompare.apiKey && + this.pixelUrl === configToCompare.pixelUrl && + checkArrayEquality(this.segmentsToCheck, configToCompare.segmentsToCheck) ); } } + +export type OdpNotIntegratedConfig = { + readonly integrated: false; +} + +export type OdpIntegratedConfig = { + readonly integrated: true; + readonly odpConfig: OdpConfig; +} + +export const odpIntegrationsAreEqual = (config1: OdpIntegrationConfig, config2: OdpIntegrationConfig): boolean => { + if (config1.integrated !== config2.integrated) { + return false; + } + + if (config1.integrated && config2.integrated) { + return config1.odpConfig.equals(config2.odpConfig); + } + + return true; +} + +export type OdpIntegrationConfig = OdpNotIntegratedConfig | OdpIntegratedConfig; diff --git a/lib/core/odp/odp_event_api_manager.ts b/lib/core/odp/odp_event_api_manager.ts index 30d5eb7a1..35ffcc4e8 100644 --- a/lib/core/odp/odp_event_api_manager.ts +++ b/lib/core/odp/odp_event_api_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,15 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { OdpEvent } from './odp_event'; import { RequestHandler } from '../../utils/http_request_handler/http'; import { OdpConfig } from './odp_config'; +import { ERROR_MESSAGES } from '../../utils/enums'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; -export const ODP_CONFIG_NOT_READY_MESSAGE = 'ODP config not ready'; /** * Manager for communicating with the Optimizely Data Platform REST API */ export interface IOdpEventApiManager { - sendEvents(events: OdpEvent[]): Promise<boolean>; - updateSettings(odpConfig: OdpConfig): void; + sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<boolean>; } /** @@ -46,11 +45,6 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { */ private readonly requestHandler: RequestHandler; - /** - * ODP configuration settings for identifying the target API and segments - */ - protected odpConfig?: OdpConfig; - /** * Creates instance to access Optimizely Data Platform (ODP) REST API * @param requestHandler Desired request handler for testing @@ -61,14 +55,6 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { this.logger = logger; } - /** - * Updates odpConfig of the api manager instance - * @param odpConfig - */ - updateSettings(odpConfig: OdpConfig): void { - this.odpConfig = odpConfig; - } - getLogger(): LogHandler { return this.logger; } @@ -78,14 +64,9 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { * @param events ODP events to send * @returns Retry is true - if network or server error (5xx), otherwise false */ - async sendEvents(events: OdpEvent[]): Promise<boolean> { + async sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<boolean> { let shouldRetry = false; - if (!this.odpConfig?.isReady()) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${ODP_CONFIG_NOT_READY_MESSAGE})`); - return shouldRetry; - } - if (events.length === 0) { this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (no events)`); return shouldRetry; @@ -95,7 +76,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { return shouldRetry; } - const { method, endpoint, headers, data } = this.generateRequestData(events); + const { method, endpoint, headers, data } = this.generateRequestData(odpConfig, events); let statusCode = 0; try { @@ -125,6 +106,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { protected abstract shouldSendEvents(events: OdpEvent[]): boolean; protected abstract generateRequestData( + odpConfig: OdpConfig, events: OdpEvent[] ): { method: string; diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 934c2d2fb..80baa4822 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,9 @@ const MAX_RETRIES = 3; /** * Event dispatcher's execution states */ -export enum STATE { - STOPPED, - RUNNING, - PROCESSING, +export enum Status { + Stopped, + Running, } /** @@ -52,7 +51,7 @@ export interface IOdpEventManager { sendEvent(event: OdpEvent): void; - flush(): void; + flush(retry?: boolean): void; } /** @@ -62,7 +61,7 @@ export abstract class OdpEventManager implements IOdpEventManager { /** * Current state of the event processor */ - state: STATE = STATE.STOPPED; + status: Status = Status.Stopped; /** * Queue for holding all events to be eventually dispatched @@ -80,7 +79,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * ODP configuration settings for identifying the target API and segments * @private */ - private odpConfig: OdpConfig; + private odpConfig?: OdpConfig; /** * REST API Manager used to send the events @@ -130,6 +129,8 @@ export abstract class OdpEventManager implements IOdpEventManager { */ private readonly userAgentParser?: IUserAgentParser; + private retries: number; + /** * Information about the user agent @@ -147,8 +148,9 @@ export abstract class OdpEventManager implements IOdpEventManager { batchSize, flushInterval, userAgentParser, + retries, }: { - odpConfig: OdpConfig; + odpConfig?: OdpConfig; apiManager: IOdpEventApiManager; logger: LogHandler; clientEngine: string; @@ -157,15 +159,16 @@ export abstract class OdpEventManager implements IOdpEventManager { batchSize?: number; flushInterval?: number; userAgentParser?: IUserAgentParser; + retries?: number; }) { - this.odpConfig = odpConfig; this.apiManager = apiManager; this.logger = logger; this.clientEngine = clientEngine; this.clientVersion = clientVersion; this.initParams(batchSize, queueSize, flushInterval); - this.state = STATE.STOPPED; + this.status = Status.Stopped; this.userAgentParser = userAgentParser; + this.retries = retries || MAX_RETRIES; if (userAgentParser) { const { os, device } = userAgentParser.parseUserAgentInfo(); @@ -182,7 +185,9 @@ export abstract class OdpEventManager implements IOdpEventManager { ); } - this.apiManager.updateSettings(odpConfig); + if (odpConfig) { + this.updateSettings(odpConfig); + } } protected abstract initParams( @@ -195,25 +200,38 @@ export abstract class OdpEventManager implements IOdpEventManager { * Update ODP configuration settings. * @param newConfig New configuration to apply */ - updateSettings(newConfig: OdpConfig): void { - this.odpConfig = newConfig; - this.apiManager.updateSettings(newConfig); + updateSettings(odpConfig: OdpConfig): void { + // do nothing if config did not change + if (this.odpConfig && this.odpConfig.equals(odpConfig)) { + return; + } + + this.flush(); + this.odpConfig = odpConfig; } /** - * Cleans up all pending events; occurs every time the ODP Config is updated. + * Cleans up all pending events; */ flush(): void { this.processQueue(true); } /** - * Start processing events in the queue + * Start the event manager */ start(): void { - this.state = STATE.RUNNING; + if (!this.odpConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return; + } - this.setNewTimeout(); + this.status = Status.Running; + + // no need of periodic flush if batchSize is 1 + if (this.batchSize > 1) { + this.setNewTimeout(); + } } /** @@ -222,9 +240,9 @@ export abstract class OdpEventManager implements IOdpEventManager { async stop(): Promise<void> { this.logger.log(LogLevel.DEBUG, 'Stop requested.'); - await this.processQueue(true); - - this.state = STATE.STOPPED; + this.flush(); + this.clearCurrentTimeout(); + this.status = Status.Stopped; this.logger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', this.queue.length); } @@ -283,7 +301,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * @private */ private enqueue(event: OdpEvent): void { - if (this.state === STATE.STOPPED) { + if (this.status === Status.Stopped) { this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.'); return; } @@ -303,7 +321,6 @@ export abstract class OdpEventManager implements IOdpEventManager { } this.queue.push(event); - this.processQueue(); } @@ -315,38 +332,30 @@ export abstract class OdpEventManager implements IOdpEventManager { * @private */ private processQueue(shouldFlush = false): void { - if (this.state !== STATE.RUNNING) { + if (this.status !== Status.Running) { return; } - - if (!this.isOdpConfigurationReady()) { - return; - } - - // Flush interval occurred & queue has items + if (shouldFlush) { // clear the queue completely this.clearCurrentTimeout(); - this.state = STATE.PROCESSING; - while (this.queueContainsItems()) { this.makeAndSend1Batch(); } - } - // Check if queue has a full batch available - else if (this.queueHasBatches()) { + } else if (this.queueHasBatches()) { + // Check if queue has a full batch available this.clearCurrentTimeout(); - this.state = STATE.PROCESSING; - while (this.queueHasBatches()) { this.makeAndSend1Batch(); } } - this.state = STATE.RUNNING; - this.setNewTimeout(); + // no need for periodic flush if batchSize is 1 + if (this.batchSize > 1) { + this.setNewTimeout(); + } } /** @@ -374,27 +383,23 @@ export abstract class OdpEventManager implements IOdpEventManager { * @private */ private makeAndSend1Batch(): void { - const batch = new Array<OdpEvent>(); - - // remove a batch from the queue - for (let count = 0; count < this.batchSize; count += 1) { - const event = this.queue.shift(); - if (event) { - batch.push(event); - } else { - break; - } + if (!this.odpConfig) { + return; } + const batch = this.queue.splice(0, this.batchSize); + + const odpConfig = this.odpConfig; + if (batch.length > 0) { // put sending the event on another event loop - setTimeout(async () => { + queueMicrotask(async () => { let shouldRetry: boolean; let attemptNumber = 0; do { - shouldRetry = await this.apiManager.sendEvents(batch); + shouldRetry = await this.apiManager.sendEvents(odpConfig, batch); attemptNumber += 1; - } while (shouldRetry && attemptNumber < MAX_RETRIES); + } while (shouldRetry && attemptNumber < this.retries); }); } } @@ -417,20 +422,6 @@ export abstract class OdpEventManager implements IOdpEventManager { return this.queue.length > 0; } - /** - * Check if the ODP Configuration is ready and log if not. - * Potentially clear queue if server-side - * @returns True if the ODP configuration is ready otherwise False - * @private - */ - private isOdpConfigurationReady(): boolean { - if (this.odpConfig.isReady()) { - return true; - } - this.discardEventsIfNeeded(); - return false; - } - protected abstract discardEventsIfNeeded(): void; /** @@ -454,4 +445,8 @@ export abstract class OdpEventManager implements IOdpEventManager { protected getLogger(): LogHandler { return this.logger; } + + getQueue(): OdpEvent[] { + return this.queue; + } } diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index ca3fc7f77..4da2f6191 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,29 +20,26 @@ import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; import { VuidManager } from '../../plugins/vuid_manager'; -import { OdpConfig } from './odp_config'; +import { OdpConfig, OdpIntegrationConfig, odpIntegrationsAreEqual } from './odp_config'; import { IOdpEventManager } from './odp_event_manager'; import { IOdpSegmentManager } from './odp_segment_manager'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { invalidOdpDataFound } from './odp_utils'; import { OdpEvent } from './odp_event'; +import { resolvablePromise, ResolvablePromise } from '../../utils/promise/resolvablePromise'; /** * Manager for handling internal all business logic related to * Optimizely Data Platform (ODP) / Advanced Audience Targeting (AAT) */ export interface IOdpManager { - initPromise?: Promise<void>; + onReady(): Promise<unknown>; - enabled: boolean; + isReady(): boolean; - segmentManager: IOdpSegmentManager | undefined; + updateSettings(odpIntegrationConfig: OdpIntegrationConfig): boolean; - eventManager: IOdpEventManager | undefined; - - updateSettings({ apiKey, apiHost, pixelUrl, segmentsToCheck }: OdpConfig): boolean; - - close(): void; + stop(): void; fetchQualifiedSegments(userId: string, options?: Array<OptimizelySegmentOption>): Promise<string[] | null>; @@ -55,6 +52,11 @@ export interface IOdpManager { getVuid(): string | undefined; } +export enum Status { + Running, + Stopped, +} + /** * Orchestrates segments manager, event manager, and ODP configuration */ @@ -62,79 +64,143 @@ export abstract class OdpManager implements IOdpManager { /** * Promise that returns when the OdpManager is finished initializing */ - initPromise?: Promise<void>; + private initPromise: Promise<unknown>; + + private ready = false; /** - * Switch to enable/disable ODP Manager functionality + * Promise that resolves when odpConfig becomes available */ - enabled = true; + private configPromise: ResolvablePromise<void>; + + status: Status = Status.Stopped; /** * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. * It fetches all qualified segments for the given user context and manages the segments cache for all user contexts. */ - segmentManager: IOdpSegmentManager | undefined; + private segmentManager: IOdpSegmentManager; /** * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. */ - eventManager: IOdpEventManager | undefined; + private eventManager: IOdpEventManager; /** * Handler for recording execution logs * @protected */ - protected logger: LogHandler = getLogger(); // TODO: Consider making private and moving instantiation to constructor + protected logger: LogHandler; /** * ODP configuration settings for identifying the target API and segments */ - odpConfig: OdpConfig = new OdpConfig(); // TODO: Consider making private and adding public accessors + odpIntegrationConfig?: OdpIntegrationConfig; + + // TODO: Consider accepting logger as a parameter and initializing it in constructor instead + constructor({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + }: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + }) { + this.segmentManager = segmentManager; + this.eventManager = eventManager; + this.logger = logger; + + this.configPromise = resolvablePromise(); + + const readinessDependencies: PromiseLike<unknown>[] = [this.configPromise]; + + if (this.isVuidEnabled()) { + readinessDependencies.push(this.initializeVuid()); + } - constructor() {} // TODO: Consider accepting logger as a parameter and initializing it in constructor instead + this.initPromise = Promise.all(readinessDependencies); - /** - * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments - */ - updateSettings({ apiKey, apiHost, pixelUrl, segmentsToCheck }: OdpConfig): boolean { - if (!this.enabled) { - return false; + this.onReady().then(() => { + this.ready = true; + if(this.isVuidEnabled() && this.status === Status.Running) { + this.registerVuid(); + } + }); + + if (odpIntegrationConfig) { + this.updateSettings(odpIntegrationConfig); } + } - if (!this.eventManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING); - return false; + public getStatus(): Status { + return this.status; + } + + async start(): Promise<void> { + if (this.status === Status.Running) { + return; } - if (!this.segmentManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING); - return false; + if (!this.odpIntegrationConfig) { + return Promise.reject(new Error('cannot start without ODP config')); } - this.eventManager.flush(); + if (!this.odpIntegrationConfig.integrated) { + return Promise.reject(new Error('start() called when ODP is not integrated')); + } - const newConfig = new OdpConfig(apiKey, apiHost, pixelUrl, segmentsToCheck); - const configDidUpdate = this.odpConfig.update(newConfig); + this.status = Status.Running; + this.segmentManager.updateSettings(this.odpIntegrationConfig.odpConfig); + this.eventManager.updateSettings(this.odpIntegrationConfig.odpConfig); + this.eventManager.start(); + return Promise.resolve(); + } - if (configDidUpdate) { - this.odpConfig.update(newConfig); - this.segmentManager?.reset(); - return true; + async stop(): Promise<void> { + if (this.status === Status.Stopped) { + return; } + this.status = Status.Stopped; + await this.eventManager.stop(); + } + + onReady(): Promise<unknown> { + return this.initPromise; + } - return false; + isReady(): boolean { + return this.ready; } /** - * Attempts to stop the current instance of ODP Manager's event manager, if it exists and is running. + * Provides a method to update ODP Manager's ODP Config */ - close(): void { - if (!this.enabled) { - return; + updateSettings(odpIntegrationConfig: OdpIntegrationConfig): boolean { + this.configPromise.resolve(); + + // do nothing if config did not change + if (this.odpIntegrationConfig && odpIntegrationsAreEqual(this.odpIntegrationConfig, odpIntegrationConfig)) { + return false; } - this.eventManager?.stop(); + this.odpIntegrationConfig = odpIntegrationConfig; + + if (odpIntegrationConfig.integrated) { + // already running, just propagate updated config to children; + if (this.status === Status.Running) { + this.segmentManager.updateSettings(odpIntegrationConfig.odpConfig); + this.eventManager.updateSettings(odpIntegrationConfig.odpConfig); + } else { + this.start(); + } + } else { + this.stop(); + } + return true; } /** @@ -145,13 +211,13 @@ export abstract class OdpManager implements IOdpManager { * @returns {Promise<string[] | null>} A promise holding either a list of qualified segments or null. */ async fetchQualifiedSegments(userId: string, options: Array<OptimizelySegmentOption> = []): Promise<string[] | null> { - if (!this.enabled) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return null; } - if (!this.segmentManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING); + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return null; } @@ -159,7 +225,7 @@ export abstract class OdpManager implements IOdpManager { return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, userId, options); } - return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); + return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); } /** @@ -169,18 +235,13 @@ export abstract class OdpManager implements IOdpManager { * @returns */ identifyUser(userId?: string, vuid?: string): void { - if (!this.enabled) { - this.logger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED); - return; - } - - if (!this.odpConfig.isReady()) { - this.logger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return; } - if (!this.eventManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING); + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return; } @@ -203,22 +264,20 @@ export abstract class OdpManager implements IOdpManager { mType = 'fullstack'; } - if (!this.enabled) { - throw new Error(ERROR_MESSAGES.ODP_NOT_ENABLED); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return; } - if (!this.odpConfig.isReady()) { - throw new Error(ERROR_MESSAGES.ODP_NOT_INTEGRATED); + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + return; } if (invalidOdpDataFound(data)) { throw new Error(ERROR_MESSAGES.ODP_INVALID_DATA); } - if (!this.eventManager) { - throw new Error(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING); - } - if (typeof action !== 'string' || action === '') { throw new Error('ODP action is not valid (cannot be empty).'); } @@ -235,4 +294,31 @@ export abstract class OdpManager implements IOdpManager { * Returns VUID value if it exists */ abstract getVuid(): string | undefined; + + protected initializeVuid(): Promise<void> { + return Promise.resolve(); + } + + private registerVuid() { + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return; + } + + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + return; + } + + const vuid = this.getVuid(); + if (!vuid) { + return; + } + + try { + this.eventManager.registerVuid(vuid); + } catch (e) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED); + } + } } diff --git a/lib/core/odp/odp_segment_manager.ts b/lib/core/odp/odp_segment_manager.ts index 1d89fd467..ac92f5e33 100644 --- a/lib/core/odp/odp_segment_manager.ts +++ b/lib/core/odp/odp_segment_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ export class OdpSegmentManager implements IOdpSegmentManager { * ODP configuration settings in used * @private */ - private odpConfig: OdpConfig; + private odpConfig?: OdpConfig; /** * Holds cached audience segments @@ -69,10 +69,10 @@ export class OdpSegmentManager implements IOdpSegmentManager { private readonly logger: LogHandler; constructor( - odpConfig: OdpConfig, segmentsCache: ICache<string, string[]>, odpSegmentApiManager: IOdpSegmentApiManager, - logger?: LogHandler + logger?: LogHandler, + odpConfig?: OdpConfig, ) { this.odpConfig = odpConfig; this._segmentsCache = segmentsCache; @@ -93,11 +93,9 @@ export class OdpSegmentManager implements IOdpSegmentManager { userValue: string, options: Array<OptimizelySegmentOption> ): Promise<string[] | null> { - const { apiHost: odpApiHost, apiKey: odpApiKey } = this.odpConfig; - - if (!odpApiKey || !odpApiHost) { - this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER); - return null; + if (!this.odpConfig) { + this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return null; } const segmentsToCheck = this.odpConfig.segmentsToCheck; @@ -127,8 +125,8 @@ export class OdpSegmentManager implements IOdpSegmentManager { this.logger.log(LogLevel.DEBUG, `Making a call to ODP server.`); const segments = await this.odpSegmentApiManager.fetchSegments( - odpApiKey, - odpApiHost, + this.odpConfig.apiKey, + this.odpConfig.apiHost, userKey, userValue, segmentsToCheck @@ -164,6 +162,6 @@ export class OdpSegmentManager implements IOdpSegmentManager { */ updateSettings(config: OdpConfig): void { this.odpConfig = config; - this._segmentsCache.reset(); + this.reset(); } } diff --git a/lib/core/project_config/index.tests.js b/lib/core/project_config/index.tests.js index 0500e8884..24134e3cd 100644 --- a/lib/core/project_config/index.tests.js +++ b/lib/core/project_config/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2023, Optimizely + * Copyright 2016-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -813,21 +813,12 @@ describe('lib/core/project_config', function() { assert.equal(config.integrations.length, 4); }); - it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp); - }); - - it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp); - }); - - it('should populate the pixelUrl value from the odp integration', () => { - assert.exists(config.pixelUrlForOdp); - }); - - it('should contain all expected unique odp segments in allSegments', () => { - assert.equal(config.allSegments.length, 3); - assert.deepEqual(config.allSegments, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']); + it('should populate odpIntegrationConfig', () => { + assert.isTrue(config.odpIntegrationConfig.integrated); + assert.equal(config.odpIntegrationConfig.odpConfig.apiKey, 'W4WzcEs-ABgXorzY7h1LCQ'); + assert.equal(config.odpIntegrationConfig.odpConfig.apiHost, 'https://api.zaius.com/'); + assert.equal(config.odpIntegrationConfig.odpConfig.pixelUrl, 'https://jumbe.zaius.com/'); + assert.deepEqual(config.odpIntegrationConfig.odpConfig.segmentsToCheck, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']); }); }); @@ -842,23 +833,12 @@ describe('lib/core/project_config', function() { assert.equal(config.integrations.length, 3); }); - it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp); - assert.equal(config.publicKeyForOdp, 'W4WzcEs-ABgXorzY7h1LCQ'); - }); - - it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp); - assert.equal(config.hostForOdp, 'https://api.zaius.com/'); - }); - - it('should populate the pixelUrl value from the odp integration', () => { - assert.exists(config.pixelUrlForOdp); - assert.equal(config.pixelUrlForOdp, 'https://jumbe.zaius.com/'); - }); - - it('should contain all expected unique odp segments in all segments', () => { - assert.equal(config.allSegments.length, 0); + it('should populate odpIntegrationConfig', () => { + assert.isTrue(config.odpIntegrationConfig.integrated); + assert.equal(config.odpIntegrationConfig.odpConfig.apiKey, 'W4WzcEs-ABgXorzY7h1LCQ'); + assert.equal(config.odpIntegrationConfig.odpConfig.apiHost, 'https://api.zaius.com/'); + assert.equal(config.odpIntegrationConfig.odpConfig.pixelUrl, 'https://jumbe.zaius.com/'); + assert.deepEqual(config.odpIntegrationConfig.odpConfig.segmentsToCheck, []); }); }); @@ -882,6 +862,11 @@ describe('lib/core/project_config', function() { it('should convert integrations from the datafile into the project config', () => { assert.equal(config.integrations.length, 0); }); + + it('should populate odpIntegrationConfig', () => { + assert.isFalse(config.odpIntegrationConfig.integrated); + assert.isUndefined(config.odpIntegrationConfig.odpConfig); + }); }); }); }); diff --git a/lib/core/project_config/index.ts b/lib/core/project_config/index.ts index b94e3373c..68ffbeacd 100644 --- a/lib/core/project_config/index.ts +++ b/lib/core/project_config/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2023, Optimizely + * Copyright 2016-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import { Integration, FeatureVariableValue, } from '../../shared_types'; +import { OdpConfig, OdpIntegrationConfig } from '../odp/odp_config'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type @@ -90,10 +91,7 @@ export interface ProjectConfig { flagVariationsMap: { [key: string]: Variation[] }; integrations: Integration[]; integrationKeyMap?: { [key: string]: Integration }; - publicKeyForOdp?: string; - hostForOdp?: string; - pixelUrlForOdp?: string; - allSegments: string[]; + odpIntegrationConfig: OdpIntegrationConfig; } const EXPERIMENT_RUNNING_STATUS = 'Running'; @@ -154,19 +152,6 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.audiencesById = keyBy(projectConfig.audiences, 'id'); assign(projectConfig.audiencesById, keyBy(projectConfig.typedAudiences, 'id')); - projectConfig.allSegments = []; - const allSegmentsSet = new Set<string>(); - - Object.keys(projectConfig.audiencesById) - .map(audience => getAudienceSegments(projectConfig.audiencesById[audience])) - .forEach(audienceSegments => { - audienceSegments.forEach(segment => { - allSegmentsSet.add(segment); - }); - }); - - projectConfig.allSegments = Array.from(allSegmentsSet); - projectConfig.attributeKeyMap = keyBy(projectConfig.attributes, 'key'); projectConfig.eventKeyMap = keyBy(projectConfig.events, 'key'); projectConfig.groupIdMap = keyBy(projectConfig.groups, 'id'); @@ -188,6 +173,23 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str }); }); + const allSegmentsSet = new Set<string>(); + + Object.keys(projectConfig.audiencesById) + .map(audience => getAudienceSegments(projectConfig.audiencesById[audience])) + .forEach(audienceSegments => { + audienceSegments.forEach(segment => { + allSegmentsSet.add(segment); + }); + }); + + const allSegments = Array.from(allSegmentsSet); + + let odpIntegrated = false; + let odpApiHost = ''; + let odpApiKey = ''; + let odpPixelUrl = ''; + if (projectConfig.integrations) { projectConfig.integrationKeyMap = keyBy(projectConfig.integrations, 'key'); @@ -197,21 +199,23 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str } if (integration.key === 'odp') { - if (integration.publicKey && !projectConfig.publicKeyForOdp) { - projectConfig.publicKeyForOdp = integration.publicKey; - } - - if (integration.host && !projectConfig.hostForOdp) { - projectConfig.hostForOdp = integration.host; - } - - if (integration.pixelUrl && !projectConfig.pixelUrlForOdp) { - projectConfig.pixelUrlForOdp = integration.pixelUrl; - } + odpIntegrated = true; + odpApiKey = odpApiKey || integration.publicKey || ''; + odpApiHost = odpApiHost || integration.host || ''; + odpPixelUrl = odpPixelUrl || integration.pixelUrl || ''; } }); } + if (odpIntegrated) { + projectConfig.odpIntegrationConfig = { + integrated: true, + odpConfig: new OdpConfig(odpApiKey, odpApiHost, odpPixelUrl, allSegments), + } + } else { + projectConfig.odpIntegrationConfig = { integrated: false }; + } + projectConfig.experimentKeyMap = keyBy(projectConfig.experiments, 'key'); projectConfig.experimentIdMap = keyBy(projectConfig.experiments, 'id'); diff --git a/lib/core/project_config/project_config_manager.tests.js b/lib/core/project_config/project_config_manager.tests.js index bc44e62eb..b8fe8f8d3 100644 --- a/lib/core/project_config/project_config_manager.tests.js +++ b/lib/core/project_config/project_config_manager.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, 2022, Optimizely + * Copyright 2019-2020, 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -165,7 +165,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); }); - it('does not call onUpdate listeners after becoming ready when constructed with a valid datafile and without sdkKey', function() { + it('calls onUpdate listeners once when constructed with a valid datafile and without sdkKey', function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); var manager = projectConfigManager.createProjectConfigManager({ datafile: configWithFeatures, @@ -173,7 +173,7 @@ describe('lib/core/project_config/project_config_manager', function() { var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); return manager.onReady().then(function() { - sinon.assert.notCalled(onUpdateSpy); + sinon.assert.calledOnce(onUpdateSpy); }); }); @@ -242,7 +242,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); }); - it('calls onUpdate listeners after becoming ready, and after the datafile manager emits updates', function() { + it('calls onUpdate listeners after becoming ready, and after the datafile manager emits updates', async function() { datafileManager.HttpPollingDatafileManager.returns({ start: sinon.stub(), stop: sinon.stub(), @@ -256,21 +256,20 @@ describe('lib/core/project_config/project_config_manager', function() { }); var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function() { - sinon.assert.calledOnce(onUpdateSpy); - - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - - updateListener({ datafile: newDatafile }); - sinon.assert.calledTwice(onUpdateSpy); - }); + await manager.onReady(); + sinon.assert.calledOnce(onUpdateSpy); + var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; + var updateListener = fakeDatafileManager.on.getCall(0).args[1]; + var newDatafile = testData.getTestProjectConfigWithFeatures(); + newDatafile.revision = '36'; + fakeDatafileManager.get.returns(newDatafile); + updateListener({ datafile: newDatafile }); + + await Promise.resolve(); + sinon.assert.calledTwice(onUpdateSpy); }); - it('can remove onUpdate listeners using the function returned from onUpdate', function() { + it('can remove onUpdate listeners using the function returned from onUpdate', async function() { datafileManager.HttpPollingDatafileManager.returns({ start: sinon.stub(), stop: sinon.stub(), @@ -282,29 +281,29 @@ describe('lib/core/project_config/project_config_manager', function() { sdkKey: '12345', datafileManager: createHttpPollingDatafileManager('12345', logger), }); - return manager.onReady().then(function() { - var onUpdateSpy = sinon.spy(); - var unsubscribe = manager.onUpdate(onUpdateSpy); + await manager.onReady(); + var onUpdateSpy = sinon.spy(); + var unsubscribe = manager.onUpdate(onUpdateSpy); + var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; + var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - - sinon.assert.calledOnce(onUpdateSpy); - - unsubscribe(); - - newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '37'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - // // Should not call onUpdateSpy again since we unsubscribed - updateListener({ datafile: testData.getTestProjectConfigWithFeatures() }); - sinon.assert.calledOnce(onUpdateSpy); - }); + var newDatafile = testData.getTestProjectConfigWithFeatures(); + newDatafile.revision = '36'; + fakeDatafileManager.get.returns(newDatafile); + + updateListener({ datafile: newDatafile }); + // allow queued micortasks to run + await Promise.resolve(); + + sinon.assert.calledOnce(onUpdateSpy); + unsubscribe(); + newDatafile = testData.getTestProjectConfigWithFeatures(); + newDatafile.revision = '37'; + fakeDatafileManager.get.returns(newDatafile); + updateListener({ datafile: newDatafile }); + // // Should not call onUpdateSpy again since we unsubscribed + updateListener({ datafile: testData.getTestProjectConfigWithFeatures() }); + sinon.assert.calledOnce(onUpdateSpy); }); it('fulfills its ready promise with an unsuccessful result when the datafile manager emits an invalid datafile', function() { @@ -368,58 +367,92 @@ describe('lib/core/project_config/project_config_manager', function() { }); describe('when constructed with sdkKey and with a valid datafile object', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners after becoming ready', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); + it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners if datafile does not change', async function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); + + const handlers = []; + const mockDatafileManager = { + start: () => {}, + get: () => JSON.stringify(configWithFeatures), + on: (event, fn) => handlers.push(fn), + onReady: () => Promise.resolve(), + pushUpdate: (datafile) => handlers.forEach(handler => handler({ datafile })), + }; + var manager = projectConfigManager.createProjectConfigManager({ datafile: configWithFeatures, sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, configWithFeatures), + datafileManager: mockDatafileManager, }); var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - // Datafile is the same as what it was constructed with, so should - // not have called update listener - sinon.assert.notCalled(onUpdateSpy); + + const result = await manager.onReady(); + assert.include(result, { + success: true, }); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + // allow queued microtasks to run + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + + configWithFeatures.revision = '99'; + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + sinon.assert.callCount(onUpdateSpy, 2); }); }); describe('when constructed with sdkKey and with a valid datafile string', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners after becoming ready', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); + it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners if datafile does not change', async function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); + + const handlers = []; + const mockDatafileManager = { + start: () => {}, + get: () => JSON.stringify(configWithFeatures), + on: (event, fn) => handlers.push(fn), + onReady: () => Promise.resolve(), + pushUpdate: (datafile) => handlers.forEach(handler => handler({ datafile })), + }; + var manager = projectConfigManager.createProjectConfigManager({ datafile: JSON.stringify(configWithFeatures), sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, JSON.stringify(configWithFeatures)), + datafileManager: mockDatafileManager, }); var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - // Datafile is the same as what it was constructed with, so should - // not have called update listener - sinon.assert.notCalled(onUpdateSpy); + + const result = await manager.onReady(); + assert.include(result, { + success: true, }); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + // allow queued microtasks to run + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + + configWithFeatures.revision = '99'; + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + sinon.assert.callCount(onUpdateSpy, 2); }); }); diff --git a/lib/core/project_config/project_config_manager.ts b/lib/core/project_config/project_config_manager.ts index 0432b5fc1..3f3aea4df 100644 --- a/lib/core/project_config/project_config_manager.ts +++ b/lib/core/project_config/project_config_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2022, Optimizely + * Copyright 2019-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,6 +89,7 @@ export class ProjectConfigManager { if (config.sdkKey && config.datafileManager) { this.datafileManager = config.datafileManager; this.datafileManager.start(); + this.readyPromise = this.datafileManager .onReady() .then(this.onDatafileManagerReadyFulfill.bind(this), this.onDatafileManagerReadyReject.bind(this)); @@ -188,7 +189,10 @@ export class ProjectConfigManager { if (configObj && oldRevision !== configObj.revision) { this.configObj = configObj; this.optimizelyConfigObj = null; - this.updateListeners.forEach(listener => listener(configObj)); + + queueMicrotask(() => { + this.updateListeners.forEach(listener => listener(configObj)); + }); } } diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 04e4a1559..e9b5283e5 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -582,6 +582,7 @@ describe('javascript-sdk (Browser)', function() { var sandbox = sinon.sandbox.create(); const fakeOptimizely = { + onReady: () => Promise.resolve({ success: true }), identifyUser: sinon.stub().returns(), }; @@ -621,12 +622,14 @@ describe('javascript-sdk (Browser)', function() { requestParams.clear(); }); - it('should send identify event by default when initialized', () => { + it('should send identify event by default when initialized', async () => { new OptimizelyUserContext({ optimizely: fakeOptimizely, userId: testFsUserId, }); + await fakeOptimizely.onReady(); + sinon.assert.calledOnce(fakeOptimizely.identifyUser); sinon.assert.calledWith(fakeOptimizely.identifyUser, testFsUserId); @@ -639,7 +642,8 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpOptions: { disabled: true }, + odpManager: BrowserOdpManager.createInstance({ logger, odpOptions: { disabled: true, @@ -657,7 +661,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpManager: BrowserOdpManager.createInstance({ logger, }), }); @@ -689,7 +693,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpManager: BrowserOdpManager.createInstance({ logger, odpOptions: { segmentsCacheSize: 10, @@ -711,7 +715,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpManager: BrowserOdpManager.createInstance({ logger, odpOptions: { segmentsCacheTimeout: 10, @@ -759,7 +763,7 @@ describe('javascript-sdk (Browser)', function() { }; const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + datafile: testData.getOdpIntegratedConfigWithSegments(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -769,9 +773,10 @@ describe('javascript-sdk (Browser)', function() { }, }); + const readyData = await client.onReady(); + sinon.assert.called(fakeSegmentManager.updateSettings); - const readyData = await client.onReady(); assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); @@ -794,7 +799,7 @@ describe('javascript-sdk (Browser)', function() { }; const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + datafile: testData.getOdpIntegratedConfigWithSegments(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -1143,7 +1148,7 @@ describe('javascript-sdk (Browser)', function() { }); it('should send odp client_initialized on client instantiation', async () => { - const odpConfig = new OdpConfig(); + const odpConfig = new OdpConfig('key', 'host', 'pixel', []); const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); sinon.spy(apiManager, 'sendEvents'); const eventManager = new BrowserOdpEventManager({ @@ -1170,7 +1175,8 @@ describe('javascript-sdk (Browser)', function() { clock.tick(100); - const [events] = apiManager.sendEvents.getCall(0).args; + const [_, events] = apiManager.sendEvents.getCall(0).args; + const [firstEvent] = events; assert.equal(firstEvent.action, 'client_initialized'); assert.equal(firstEvent.type, 'fullstack'); diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 46ce0dbe6..c0d62897c 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -131,6 +131,8 @@ const createInstance = function(config: Config): Client | null { logger.info(enums.LOG_MESSAGES.ODP_DISABLED); } + const { clientEngine, clientVersion } = config; + const optimizelyOptions: OptimizelyOptions = { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, @@ -142,7 +144,8 @@ const createInstance = function(config: Config): Client | null { : undefined, notificationCenter, isValidInstance, - odpManager: odpExplicitlyOff ? undefined : new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: odpExplicitlyOff ? undefined + : BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; const optimizely = new Optimizely(optimizelyOptions); diff --git a/lib/index.node.ts b/lib/index.node.ts index 57d72e174..50a7829b8 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2017, 2019-2024 Optimizely, Inc. and contributors * + * Copyright 2016-2017, 2019-2024 Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -107,6 +107,8 @@ const createInstance = function(config: Config): Client | null { logger.info(enums.LOG_MESSAGES.ODP_DISABLED); } + const { clientEngine, clientVersion } = config; + const optimizelyOptions = { clientEngine: enums.NODE_CLIENT_ENGINE, ...config, @@ -118,7 +120,8 @@ const createInstance = function(config: Config): Client | null { : undefined, notificationCenter, isValidInstance, - odpManager: odpExplicitlyOff ? undefined : new NodeOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: odpExplicitlyOff ? undefined + : NodeOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; return new Optimizely(optimizelyOptions); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 6584b46fb..9457052f6 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -103,6 +103,8 @@ const createInstance = function(config: Config): Client | null { logger.info(enums.LOG_MESSAGES.ODP_DISABLED); } + const { clientEngine, clientVersion } = config; + const optimizelyOptions = { clientEngine: enums.REACT_NATIVE_JS_CLIENT_ENGINE, ...config, @@ -120,7 +122,8 @@ const createInstance = function(config: Config): Client | null { : undefined, notificationCenter, isValidInstance: isValidInstance, - odpManager: odpExplicitlyOff ? undefined : new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: odpExplicitlyOff ? undefined + :BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; // If client engine is react, convert it to react native. diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 39e6c3c6e..3f5b3e232 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2023, Optimizely, Inc. and contributors * + * Copyright 2016-2024, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -10144,29 +10144,6 @@ describe('lib/optimizely', function() { fns.uuid.restore(); }); - it('should call logger with log level of "info" when odp disabled', () => { - new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - eventProcessor, - notificationCenter, - odpManager: new NodeOdpManager({ - logger: createdLogger, - odpOptions: { - disabled: true, - }, - }), - }); - - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, LOG_MESSAGES.ODP_DISABLED); - }); - it('should send an identify event when called with odp enabled', () => { // TODO }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index ef57822c2..e29d04daa 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -110,6 +110,7 @@ export default class Optimizely implements Client { this.errorHandler = config.errorHandler; this.isOptimizelyConfigValid = config.isValidInstance; this.logger = config.logger; + this.odpManager = config.odpManager; let decideOptionsArray = config.defaultDecideOptions ?? []; if (!Array.isArray(decideOptionsArray)) { @@ -145,10 +146,6 @@ export default class Optimizely implements Client { this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); - NotificationRegistry.getNotificationCenter(config.sdkKey)?.sendNotifications( - NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE - ); - this.updateOdpSettings(); }); @@ -178,29 +175,11 @@ export default class Optimizely implements Client { const eventProcessorStartedPromise = this.eventProcessor.start(); - const dependentPromises: Array<Promise<any>> = [projectConfigManagerReadyPromise, eventProcessorStartedPromise]; - - if (config.odpManager?.initPromise) { - dependentPromises.push(config.odpManager.initPromise); - } - - this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { - // If no odpManager exists yet, creates a new one - if (config.odpManager != null) { - this.odpManager = config.odpManager; - this.odpManager.eventManager?.start(); - this.updateOdpSettings(); - const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; - if (sdkKey != null) { - NotificationRegistry.getNotificationCenter( - sdkKey, - this.logger - )?.addNotificationListener(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, () => this.updateOdpSettings()); - } else { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE); - } - } - + this.readyPromise = Promise.all([ + projectConfigManagerReadyPromise, + eventProcessorStartedPromise, + config.odpManager ? config.odpManager.onReady() : Promise.resolve(), + ]).then(promiseResults => { // Only return status from project config promise because event processor promise does not return any status. return promiseResults[0]; }); @@ -209,6 +188,15 @@ export default class Optimizely implements Client { this.nextReadyTimeoutId = 0; } + + /** + * Returns the project configuration retrieved from projectConfigManager + * @return {projectConfig.ProjectConfig} + */ + getProjectConfig(): projectConfig.ProjectConfig | null { + return this.projectConfigManager.getConfig(); + } + /** * Returns a truthy value if this instance currently has a valid project config * object, and the initial configuration object that was passed into the @@ -1317,7 +1305,7 @@ export default class Optimizely implements Client { close(): Promise<{ success: boolean; reason?: string }> { try { if (this.odpManager) { - this.odpManager.close(); + this.odpManager.stop(); } this.notificationCenter.clearAllNotificationListeners(); @@ -1454,16 +1442,9 @@ export default class Optimizely implements Client { * null if provided inputs are invalid */ createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { - let userIdentifier; - - if (this.odpManager?.isVuidEnabled() && !userId) { - userIdentifier = userId || this.getVuid(); - } else { - userIdentifier = userId; - } + const userIdentifier = userId ?? this.odpManager?.getVuid(); if ( - userIdentifier === null || userIdentifier === undefined || !this.validateInputs({ user_id: userIdentifier }, attributes) ) { @@ -1684,15 +1665,13 @@ export default class Optimizely implements Client { */ private updateOdpSettings(): void { const projectConfig = this.projectConfigManager.getConfig(); - if (this.odpManager != null && projectConfig != null) { - this.odpManager.updateSettings( - new OdpConfig( - projectConfig.publicKeyForOdp, - projectConfig.hostForOdp, - projectConfig.pixelUrlForOdp, - projectConfig.allSegments - ) - ); + + if (!projectConfig) { + return; + } + + if (this.odpManager) { + this.odpManager.updateSettings(projectConfig.odpIntegrationConfig) } } @@ -1744,12 +1723,17 @@ export default class Optimizely implements Client { } } + private isOdpIntegrated(): boolean { + return this.projectConfigManager.getConfig()?.odpIntegrationConfig?.integrated ?? false; + } + /** * Identifies user with ODP server in a fire-and-forget manner. + * Should be called only after the instance is ready * @param {string} userId */ public identifyUser(userId: string): void { - if (this.odpManager && this.odpManager.enabled) { + if (this.odpManager && this.isOdpIntegrated()) { this.odpManager.identifyUser(userId); } } @@ -1767,12 +1751,7 @@ export default class Optimizely implements Client { if (!this.odpManager) { return null; } - - if (!this.odpManager.enabled) { - this.logger.error(ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING); - return null; - } - + return await this.odpManager.fetchQualifiedSegments(userId, options); } diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index de3930290..0b689237a 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2020-2023, Optimizely, Inc. and contributors * + * Copyright 2020-2024, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -64,7 +64,11 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { this.forcedDecisionsMap = {}; if (shouldIdentifyUser) { - this.identifyUser(); + this.optimizely.onReady().then(({ success }) => { + if (success) { + this.identifyUser(); + } + }); } } diff --git a/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/plugins/odp/event_api_manager/index.browser.ts index 592978f63..8a21a462c 100644 --- a/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/lib/plugins/odp/event_api_manager/index.browser.ts @@ -1,7 +1,23 @@ +/**************************************************************************** + * Copyright 2024, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; -import { ODP_CONFIG_NOT_READY_MESSAGE } from '../../../core/odp/odp_event_api_manager'; +import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; @@ -16,31 +32,19 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { return false; } - private getPixelApiEndpoint(): string { - if (!this.odpConfig?.isReady()) { - throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); - } - const pixelUrl = this.odpConfig.pixelUrl; + private getPixelApiEndpoint(odpConfig: OdpConfig): string { + const pixelUrl = odpConfig.pixelUrl; const pixelApiEndpoint = new URL(pixelApiPath, pixelUrl).href; return pixelApiEndpoint; } protected generateRequestData( + odpConfig: OdpConfig, events: OdpEvent[] ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { - // the caller should ensure odpConfig is ready before calling - if (!this.odpConfig?.isReady()) { - this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); - throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); - } - - // this cannot be cached cause OdpConfig is mutable - // and can be updated in place and it is done so in odp - // manager. We should make OdpConfig immutable and - // refacator later - const pixelApiEndpoint = this.getPixelApiEndpoint(); + const pixelApiEndpoint = this.getPixelApiEndpoint(odpConfig); - const apiKey = this.odpConfig.apiKey; + const apiKey = odpConfig.apiKey; const method = 'GET'; const event = events[0]; const url = new URL(pixelApiEndpoint); diff --git a/lib/plugins/odp/event_api_manager/index.node.ts b/lib/plugins/odp/event_api_manager/index.node.ts index 1d04bc9d3..0b8b4e3ba 100644 --- a/lib/plugins/odp/event_api_manager/index.node.ts +++ b/lib/plugins/odp/event_api_manager/index.node.ts @@ -1,23 +1,34 @@ +/**************************************************************************** + * Copyright 2024, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; -import { ODP_CONFIG_NOT_READY_MESSAGE } from '../../../core/odp/odp_event_api_manager'; export class NodeOdpEventApiManager extends OdpEventApiManager { protected shouldSendEvents(events: OdpEvent[]): boolean { return true; } protected generateRequestData( + odpConfig: OdpConfig, events: OdpEvent[] ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { - // the caller should ensure odpConfig is ready before calling - if (!this.odpConfig?.isReady()) { - this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); - throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); - } - - const apiHost = this.odpConfig.apiHost; - const apiKey = this.odpConfig.apiKey; + + const { apiHost, apiKey } = odpConfig; return { method: 'POST', diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index 171b93566..e7095364a 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,36 +35,48 @@ import { VuidManager } from './../vuid_manager/index'; import { OdpManager } from '../../core/odp/odp_manager'; import { OdpEvent } from '../../core/odp/odp_event'; -import { OdpOptions } from '../../shared_types'; +import { IOdpEventManager, OdpOptions } from '../../shared_types'; import { BrowserOdpEventApiManager } from '../odp/event_api_manager/index.browser'; import { BrowserOdpEventManager } from '../odp/event_manager/index.browser'; -import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { IOdpSegmentManager, OdpSegmentManager } from '../../core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; +import { OdpConfig, OdpIntegrationConfig } from '../../core/odp/odp_config'; interface BrowserOdpManagerConfig { + clientEngine?: string, + clientVersion?: string, logger?: LogHandler; odpOptions?: OdpOptions; + odpIntegrationConfig?: OdpIntegrationConfig; } // Client-side Browser Plugin for ODP Manager export class BrowserOdpManager extends OdpManager { static cache = new BrowserAsyncStorageCache(); + vuidManager?: VuidManager; vuid?: string; - constructor({ logger, odpOptions }: BrowserOdpManagerConfig) { - super(); + constructor(options: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + }) { + super(options); + } - this.logger = logger || getLogger(); + static createInstance({ + logger, odpOptions, odpIntegrationConfig, clientEngine, clientVersion + }: BrowserOdpManagerConfig): BrowserOdpManager { + logger = logger || getLogger(); - if (odpOptions?.disabled) { - this.initPromise = Promise.resolve(); - this.enabled = false; - this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); - return; - } + clientEngine = clientEngine || JAVASCRIPT_CLIENT_ENGINE; + clientVersion = clientVersion || CLIENT_VERSION; - const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; - const browserClientVersion = CLIENT_VERSION; + let odpConfig : OdpConfig | undefined = undefined; + if (odpIntegrationConfig?.integrated) { + odpConfig = odpIntegrationConfig.odpConfig; + } let customSegmentRequestHandler; @@ -72,24 +84,25 @@ export class BrowserOdpManager extends OdpManager { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; } else { customSegmentRequestHandler = new BrowserRequestHandler( - this.logger, + logger, odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS ); } - // Set up Segment Manager (Audience Segments GraphQL API Interface) + let segmentManager: IOdpSegmentManager; + if (odpOptions?.segmentManager) { - this.segmentManager = odpOptions.segmentManager; - this.segmentManager.updateSettings(this.odpConfig); + segmentManager = odpOptions.segmentManager; } else { - this.segmentManager = new OdpSegmentManager( - this.odpConfig, + segmentManager = new OdpSegmentManager( odpOptions?.segmentsCache || new BrowserLRUCache<string, string[]>({ maxSize: odpOptions?.segmentsCacheSize, timeout: odpOptions?.segmentsCacheTimeout, }), - new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) + new OdpSegmentApiManager(customSegmentRequestHandler, logger), + logger, + odpConfig ); } @@ -99,22 +112,22 @@ export class BrowserOdpManager extends OdpManager { customEventRequestHandler = odpOptions.eventRequestHandler; } else { customEventRequestHandler = new BrowserRequestHandler( - this.logger, + logger, odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS ); } - // Set up Events Manager (Events REST API Interface) + let eventManager: IOdpEventManager; + if (odpOptions?.eventManager) { - this.eventManager = odpOptions.eventManager; - this.eventManager.updateSettings(this.odpConfig); + eventManager = odpOptions.eventManager; } else { - this.eventManager = new BrowserOdpEventManager({ - odpConfig: this.odpConfig, - apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, this.logger), - logger: this.logger, - clientEngine: browserClientEngine, - clientVersion: browserClientVersion, + eventManager = new BrowserOdpEventManager({ + odpConfig, + apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, logger), + logger: logger, + clientEngine, + clientVersion, flushInterval: odpOptions?.eventFlushInterval, batchSize: odpOptions?.eventBatchSize, queueSize: odpOptions?.eventQueueSize, @@ -122,34 +135,21 @@ export class BrowserOdpManager extends OdpManager { }); } - this.eventManager!.start(); - - this.initPromise = this.initializeVuid(BrowserOdpManager.cache).catch(e => { - this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, e); + return new BrowserOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, }); } /** - * Upon initializing BrowserOdpManager, accesses or creates new VUID from Browser cache and registers it via the Event Manager - * @private + * @override + * accesses or creates new VUID from Browser cache */ - private async initializeVuid(cache: PersistentKeyValueCache): Promise<void> { - const vuidManager = await VuidManager.instance(cache); + protected async initializeVuid(): Promise<void> { + const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); this.vuid = vuidManager.vuid; - this.registerVuid(this.vuid); - } - - private registerVuid(vuid: string) { - if (!this.eventManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING); - return; - } - - try { - this.eventManager.registerVuid(vuid); - } catch (e) { - this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED); - } } /** diff --git a/lib/plugins/odp_manager/index.node.ts b/lib/plugins/odp_manager/index.node.ts index 4f01ededc..bdd57f1ad 100644 --- a/lib/plugins/odp_manager/index.node.ts +++ b/lib/plugins/odp_manager/index.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import { ServerLRUCache } from './../../utils/lru_cache/server_lru_cache'; import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { - LOG_MESSAGES, NODE_CLIENT_ENGINE, CLIENT_VERSION, REQUEST_TIMEOUT_ODP_EVENTS_MS, @@ -28,15 +27,19 @@ import { } from '../../utils/enums'; import { OdpManager } from '../../core/odp/odp_manager'; -import { OdpOptions } from '../../shared_types'; +import { IOdpEventManager, OdpOptions } from '../../shared_types'; import { NodeOdpEventApiManager } from '../odp/event_api_manager/index.node'; import { NodeOdpEventManager } from '../odp/event_manager/index.node'; -import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { IOdpSegmentManager, OdpSegmentManager } from '../../core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; +import { OdpConfig, OdpIntegrationConfig } from '../../core/odp/odp_config'; interface NodeOdpManagerConfig { + clientEngine?: string, + clientVersion?: string, logger?: LogHandler; odpOptions?: OdpOptions; + odpIntegrationConfig?: OdpIntegrationConfig; } /** @@ -44,20 +47,27 @@ interface NodeOdpManagerConfig { * Note: As this is still a work-in-progress. Please avoid using the Node ODP Manager. */ export class NodeOdpManager extends OdpManager { - constructor({ logger, odpOptions }: NodeOdpManagerConfig) { - super(); + constructor(options: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + }) { + super(options); + } - this.logger = logger || getLogger(); + static createInstance({ + logger, odpOptions, odpIntegrationConfig, clientEngine, clientVersion + }: NodeOdpManagerConfig): NodeOdpManager { + logger = logger || getLogger(); - if (odpOptions?.disabled) { - this.initPromise = Promise.resolve(); - this.enabled = false; - this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); - return; - } + clientEngine = clientEngine || NODE_CLIENT_ENGINE; + clientVersion = clientVersion || CLIENT_VERSION; - const nodeClientEngine = NODE_CLIENT_ENGINE; - const nodeClientVersion = CLIENT_VERSION; + let odpConfig : OdpConfig | undefined = undefined; + if (odpIntegrationConfig?.integrated) { + odpConfig = odpIntegrationConfig.odpConfig; + } let customSegmentRequestHandler; @@ -65,24 +75,25 @@ export class NodeOdpManager extends OdpManager { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; } else { customSegmentRequestHandler = new NodeRequestHandler( - this.logger, + logger, odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS ); } - // Set up Segment Manager (Audience Segments GraphQL API Interface) + let segmentManager: IOdpSegmentManager; + if (odpOptions?.segmentManager) { - this.segmentManager = odpOptions.segmentManager; - this.segmentManager.updateSettings(this.odpConfig); + segmentManager = odpOptions.segmentManager; } else { - this.segmentManager = new OdpSegmentManager( - this.odpConfig, + segmentManager = new OdpSegmentManager( odpOptions?.segmentsCache || new ServerLRUCache<string, string[]>({ maxSize: odpOptions?.segmentsCacheSize, timeout: odpOptions?.segmentsCacheTimeout, }), - new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) + new OdpSegmentApiManager(customSegmentRequestHandler, logger), + logger, + odpConfig ); } @@ -92,31 +103,35 @@ export class NodeOdpManager extends OdpManager { customEventRequestHandler = odpOptions.eventRequestHandler; } else { customEventRequestHandler = new NodeRequestHandler( - this.logger, + logger, odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS ); } - // Set up Events Manager (Events REST API Interface) + let eventManager: IOdpEventManager; + if (odpOptions?.eventManager) { - this.eventManager = odpOptions.eventManager; - this.eventManager.updateSettings(this.odpConfig); + eventManager = odpOptions.eventManager; } else { - this.eventManager = new NodeOdpEventManager({ - odpConfig: this.odpConfig, - apiManager: new NodeOdpEventApiManager(customEventRequestHandler, this.logger), - logger: this.logger, - clientEngine: nodeClientEngine, - clientVersion: nodeClientVersion, + eventManager = new NodeOdpEventManager({ + odpConfig, + apiManager: new NodeOdpEventApiManager(customEventRequestHandler, logger), + logger: logger, + clientEngine, + clientVersion, flushInterval: odpOptions?.eventFlushInterval, batchSize: odpOptions?.eventBatchSize, queueSize: odpOptions?.eventQueueSize, + userAgentParser: odpOptions?.userAgentParser, }); } - this.eventManager.start(); - - this.initPromise = Promise.resolve(); + return new NodeOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + }); } public isVuidEnabled(): boolean { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 4af80fa13..361f293d5 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -37,6 +37,7 @@ import { IOdpEventManager } from './core/odp/odp_event_manager'; import { IOdpManager } from './core/odp/odp_manager'; import { IUserAgentParser } from './core/odp/user_agent_parser'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; +import { ProjectConfig } from './core/project_config'; export interface BucketerParams { experimentId: string; @@ -369,6 +370,7 @@ export interface Client { onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }>; close(): Promise<{ success: boolean; reason?: string }>; sendOdpEvent(action: string, type?: string, identifiers?: Map<string, string>, data?: Map<string, unknown>): void; + getProjectConfig(): ProjectConfig | null; } export interface ActivateListenerPayload extends ListenerPayload { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 95fe4f4ed..3f7bee937 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -31,7 +31,6 @@ export const ERROR_MESSAGES = { DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', - FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER: '%s: Audience segments fetch failed. (invalid identifier)', FETCH_SEGMENTS_FAILED_NETWORK_ERROR: '%s: Audience segments fetch failed. (network error)', FETCH_SEGMENTS_FAILED_DECODE_ERROR: '%s: Audience segments fetch failed. (decode error)', IMPROPERLY_FORMATTED_EXPERIMENT: '%s: Experiment key %s is improperly formatted.', @@ -56,6 +55,7 @@ export const ERROR_MESSAGES = { NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', + ODP_CONFIG_NOT_AVAILABLE: '%s: ODP is not integrated to the project.', ODP_EVENT_FAILED: 'ODP event send failed.', ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING: '%s: ODP unable to fetch qualified segments (Segments Manager not initialized).', @@ -79,8 +79,6 @@ export const ERROR_MESSAGES = { '%s: ODP send event %s was not dispatched (Event Manager not instantiated).', ODP_SEND_EVENT_FAILED_UID_MISSING: '%s: ODP send event %s was not dispatched (No valid user identifier provided).', ODP_SEND_EVENT_FAILED_VUID_MISSING: '%s: ODP send event %s was not dispatched (Unable to fetch VUID).', - ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE: - '%s: You must provide an sdkKey. Cannot start Notification Center for ODP Integration.', ODP_VUID_INITIALIZATION_FAILED: '%s: ODP VUID initialization failed.', ODP_VUID_REGISTRATION_FAILED: '%s: ODP VUID failed to be registered.', ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING: '%s: ODP register vuid failed. (Event Manager not instantiated).', diff --git a/lib/utils/promise/resolvablePromise.ts b/lib/utils/promise/resolvablePromise.ts new file mode 100644 index 000000000..354df2b7d --- /dev/null +++ b/lib/utils/promise/resolvablePromise.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const noop = () => {}; + +export type ResolvablePromise<T> = { + promise: Promise<T>; + resolve: (value: T | PromiseLike<T>) => void; + reject: (reason?: any) => void; + then: Promise<T>['then']; +}; + +export function resolvablePromise<T>(): ResolvablePromise<T> { + let resolve: (value: T | PromiseLike<T>) => void = noop; + let reject: (reason?: any) => void = noop; + const promise = new Promise<T>((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject, then: promise.then.bind(promise) }; +} diff --git a/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts index bfe813af6..c989b76a6 100644 --- a/tests/odpEventApiManager.spec.ts +++ b/tests/odpEventApiManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,6 @@ describe('NodeOdpEventApiManager', () => { const managerInstance = () => { const manager = new NodeOdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); - manager.updateSettings(odpConfig); return manager; } @@ -78,7 +77,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(false); verify(mockLogger.log(anything(), anyString())).never(); @@ -90,7 +89,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(false); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (400)')).once(); @@ -102,7 +101,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(true); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (500)')).once(); @@ -115,13 +114,13 @@ describe('NodeOdpEventApiManager', () => { }); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(true); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (Request timed out)')).once(); }); - it('should send events to updated host on settings update', async () => { + it('should send events to the correct host using correct api key', async () => { when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ abort: () => {}, responsePromise: Promise.reject(new Error('Request timed out')), @@ -129,24 +128,12 @@ describe('NodeOdpEventApiManager', () => { const manager = managerInstance(); - await manager.sendEvents(ODP_EVENTS); + await manager.sendEvents(odpConfig, ODP_EVENTS); - const updatedOdpConfig = new OdpConfig( - 'updated-key', - 'https://updatedhost.test/', - 'https://updatedpixel.test/', - ['updated-seg'], - ) + verify(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).once(); - manager.updateSettings(updatedOdpConfig); - await manager.sendEvents(ODP_EVENTS); - - verify(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).twice(); - - const [initUrl] = capture(mockRequestHandler.makeRequest).first(); + const [initUrl, headers] = capture(mockRequestHandler.makeRequest).first(); expect(initUrl).toEqual(`${API_HOST}/v3/events`); - - const [finalUrl] = capture(mockRequestHandler.makeRequest).last(); - expect(finalUrl).toEqual(`${updatedOdpConfig.apiHost}/v3/events`); + expect(headers['x-api-key']).toEqual(odpConfig.apiKey); }); }); diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 59a3d7669..31bc7a753 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,19 @@ * limitations under the License. */ -import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE } from '../lib/utils/enums'; +import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE, ERROR_MESSAGES } from '../lib/utils/enums'; import { OdpConfig } from '../lib/core/odp/odp_config'; -import { STATE } from '../lib/core/odp/odp_event_manager'; +import { Status } from '../lib/core/odp/odp_event_manager'; import { BrowserOdpEventManager } from "../lib/plugins/odp/event_manager/index.browser"; -import { NodeOdpEventManager, NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; +import { NodeOdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; +import { OdpEventManager } from '../lib/core/odp/odp_event_manager'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; import { IOdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; import { LogHandler, LogLevel } from '../lib/modules/logging'; import { OdpEvent } from '../lib/core/odp/odp_event'; import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; +import exp from 'constants'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; @@ -135,6 +137,20 @@ const abortableRequest = (statusCode: number, body: string) => { }; }; +class TestOdpEventManager extends OdpEventManager { + constructor(options: any) { + super(options); + } + protected initParams(batchSize: number, queueSize: number, flushInterval: number): void { + this.queueSize = queueSize; + this.batchSize = batchSize; + this.flushInterval = flushInterval; + } + protected discardEventsIfNeeded(): void { + } + protected hasNecessaryIdentifiers = (event: OdpEvent): boolean => event.identifiers.size >= 0; +} + describe('OdpEventManager', () => { let mockLogger: LogHandler; let mockApiManager: IOdpEventApiManager; @@ -152,35 +168,27 @@ describe('OdpEventManager', () => { }); beforeEach(() => { + jest.useFakeTimers(); resetCalls(mockLogger); resetCalls(mockApiManager); }); - it('should update api manager setting with odp config on instantiation', () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); - - const apiManager = instance(mockApiManager); - - const eventManager = new OdpEventManager({ - odpConfig, + it('should log an error and not start if start() is called without a config', () => { + const eventManager = new TestOdpEventManager({ + odpConfig: undefined, apiManager, logger, clientEngine, clientVersion, }); - const [passedConfig] = capture(mockApiManager.updateSettings).last(); - expect(passedConfig).toEqual(odpConfig); + eventManager.start(); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).once(); + expect(eventManager.status).toEqual(Status.Stopped); }); - it('should update api manager setting with updatetd odp config on updateSettings', () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); - - const apiManager = instance(mockApiManager); - - const eventManager = new OdpEventManager({ + it('should start() correctly after odpConfig is provided', () => { + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -188,67 +196,39 @@ describe('OdpEventManager', () => { clientVersion, }); - const updatedOdpConfig = new OdpConfig( - 'updated-key', - 'https://updatedhost.test/', - 'https://pixel.test/', - ['updated-seg'], - ) - - eventManager.updateSettings(updatedOdpConfig); - - verify(mockApiManager.updateSettings(anything())).twice(); - - const [initConfig] = capture(mockApiManager.updateSettings).first(); - expect(initConfig).toEqual(odpConfig); - - const [finalConfig] = capture(mockApiManager.updateSettings).last(); - expect(finalConfig).toEqual(updatedOdpConfig); + expect(eventManager.status).toEqual(Status.Stopped); + eventManager.updateSettings(odpConfig); + eventManager.start(); + expect(eventManager.status).toEqual(Status.Running); }); - it('should log and discard events when event manager not running', () => { - const eventManager = new OdpEventManager({ + it('should log and discard events when event manager is not running', () => { + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, }); - // since we've not called start() then... + expect(eventManager.status).toEqual(Status.Stopped); eventManager.sendEvent(EVENTS[0]); - - // ...we should get a notice after trying to send an event verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.')).once(); + expect(eventManager.getQueue().length).toEqual(0); }); - it('should log and discard events when event manager config is not ready', () => { - const mockOdpConfig = mock<OdpConfig>(); - when(mockOdpConfig.isReady()).thenReturn(false); - const odpConfig = instance(mockOdpConfig); - const eventManager = new OdpEventManager({ + it('should discard events with invalid data', () => { + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, }); - eventManager['state'] = STATE.RUNNING; // simulate running without calling start() - - eventManager.sendEvent(EVENTS[0]); + eventManager.start(); - // In a Node context, the events should be discarded - verify(mockLogger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.')).once(); - }); + expect(eventManager.status).toEqual(Status.Running); - it('should discard events with invalid data', () => { - const eventManager = new OdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); // make an event with invalid data key-value entry const badEvent = new OdpEvent( 't3', @@ -262,11 +242,12 @@ describe('OdpEventManager', () => { eventManager.sendEvent(badEvent); verify(mockLogger.log(LogLevel.ERROR, 'Event data found to be invalid.')).once(); + expect(eventManager.getQueue().length).toEqual(0); }); it('should log a max queue hit and discard ', () => { // set queue to maximum of 1 - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -274,9 +255,10 @@ describe('OdpEventManager', () => { clientVersion, queueSize: 1, // With max queue size set to 1... }); - eventManager['state'] = STATE.RUNNING; - eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... + eventManager.start(); + + eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... // ...try adding the second event eventManager.sendEvent(EVENTS[1]); @@ -286,13 +268,15 @@ describe('OdpEventManager', () => { }); it('should add additional information to each event', () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, }); + eventManager.start(); + const processedEventData = PROCESSED_EVENTS[0].data; const eventData = eventManager['augmentCommonData'](EVENTS[0].data); @@ -309,28 +293,56 @@ describe('OdpEventManager', () => { expect(eventData.get('key-4')).toEqual(processedEventData.get('key-4')); }); - it('should attempt to flush an empty queue at flush intervals', async () => { - const eventManager = new OdpEventManager({ + it('should attempt to flush an empty queue at flush intervals if batchSize is greater than 1', async () => { + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, + batchSize: 10, flushInterval: 100, }); - const spiedEventManager = spy(eventManager); + + //@ts-ignore + const processQueueSpy = jest.spyOn(eventManager, 'processQueue'); eventManager.start(); // do not add events to the queue, but allow for... - await pause(400); // at least 3 flush intervals executions (giving a little longer) + jest.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) + + expect(processQueueSpy).toHaveBeenCalledTimes(3); + }); - verify(spiedEventManager['processQueue'](anything())).atLeast(3); + + it('should not flush periodically if batch size is 1', async () => { + const eventManager = new TestOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 1, + flushInterval: 100, + }); + + //@ts-ignore + const processQueueSpy = jest.spyOn(eventManager, 'processQueue'); + + eventManager.start(); + eventManager.sendEvent(EVENTS[0]); + eventManager.sendEvent(EVENTS[1]); + + jest.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) + + expect(processQueueSpy).toHaveBeenCalledTimes(2); }); - it('should dispatch events in correct number of batches', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); + it('should dispatch events in correct batch sizes', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -341,18 +353,24 @@ describe('OdpEventManager', () => { }); eventManager.start(); + for (let i = 0; i < 25; i += 1) { eventManager.sendEvent(makeEvent(i)); } - await pause(1500); + jest.runAllTicks(); + // as we are not advancing the jest fake timers, no flush should occur // ...there should be 3 batches: // batch #1 with 10, batch #2 with 10, and batch #3 (after flushInterval lapsed) with 5 = 25 events - verify(mockApiManager.sendEvents(anything())).thrice(); + verify(mockApiManager.sendEvents(anything(), anything())).twice(); + + // rest of the events should now be flushed + jest.advanceTimersByTime(250); + verify(mockApiManager.sendEvents(anything(), anything())).thrice(); }); it('should dispatch events with correct payload', async () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -364,11 +382,11 @@ describe('OdpEventManager', () => { eventManager.start(); EVENTS.forEach(event => eventManager.sendEvent(event)); - await pause(1000); + jest.advanceTimersByTime(100); // sending 1 batch of 2 events after flushInterval since batchSize is 10 - verify(mockApiManager.sendEvents(anything())).once(); - const [events] = capture(mockApiManager.sendEvents).last(); + verify(mockApiManager.sendEvents(anything(), anything())).once(); + const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toEqual(2); expect(events[0].identifiers.size).toEqual(PROCESSED_EVENTS[0].identifiers.size); expect(events[0].data.size).toEqual(PROCESSED_EVENTS[0].data.size); @@ -376,6 +394,28 @@ describe('OdpEventManager', () => { expect(events[1].data.size).toEqual(PROCESSED_EVENTS[1].data.size); }); + it('should dispatch events with correct odpConfig', async () => { + const eventManager = new TestOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 10, + flushInterval: 100, + }); + + eventManager.start(); + EVENTS.forEach(event => eventManager.sendEvent(event)); + + jest.advanceTimersByTime(100); + + // sending 1 batch of 2 events after flushInterval since batchSize is 10 + verify(mockApiManager.sendEvents(anything(), anything())).once(); + const [usedOdpConfig] = capture(mockApiManager.sendEvents).last(); + expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); + }); + it('should augment events with data from user agent parser', async () => { const userAgentParser : IUserAgentParser = { parseUserAgentInfo: function (): UserAgentInfo { @@ -386,7 +426,7 @@ describe('OdpEventManager', () => { } } - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -399,10 +439,10 @@ describe('OdpEventManager', () => { eventManager.start(); EVENTS.forEach(event => eventManager.sendEvent(event)); - await pause(1000); + jest.advanceTimersByTime(100); - verify(mockApiManager.sendEvents(anything())).called(); - const [events] = capture(mockApiManager.sendEvents).last(); + verify(mockApiManager.sendEvents(anything(), anything())).called(); + const [_, events] = capture(mockApiManager.sendEvents).last(); const event = events[0]; expect(event.data.get('os')).toEqual('windows'); @@ -412,62 +452,175 @@ describe('OdpEventManager', () => { }); it('should retry failed events', async () => { - // all events should fail ie shouldRetry = true - when(mockApiManager.sendEvents(anything())).thenResolve(true); + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(true) + + const retries = 3; const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, - batchSize: 2, // batch size of 2 + batchSize: 2, flushInterval: 100, + retries, }); eventManager.start(); - // send 4 events for (let i = 0; i < 4; i += 1) { eventManager.sendEvent(makeEvent(i)); } - await pause(1500); - // retry 3x (default) for 2 batches or 6 calls to attempt to process - verify(mockApiManager.sendEvents(anything())).times(6); + jest.runAllTicks(); + jest.useRealTimers(); + await pause(100); + + // retry 3x for 2 batches or 6 calls to attempt to process + verify(mockApiManager.sendEvents(anything(), anything())).times(6); + }); + + it('should flush all queued events when flush() is called', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + + const apiManager = instance(mockApiManager); + const eventManager = new TestOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 200, + flushInterval: 100, + }); + + eventManager.start(); + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + + expect(eventManager.getQueue().length).toEqual(25); + + eventManager.flush(); + + jest.runAllTicks(); + + verify(mockApiManager.sendEvents(anything(), anything())).once(); + expect(eventManager.getQueue().length).toEqual(0); + }); + + it('should flush all queued events before stopping', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + const apiManager = instance(mockApiManager); + const eventManager = new TestOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 200, + flushInterval: 100, + }); + + eventManager.start(); + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + + expect(eventManager.getQueue().length).toEqual(25); + + eventManager.flush(); + + jest.runAllTicks(); + + verify(mockApiManager.sendEvents(anything(), anything())).once(); + expect(eventManager.getQueue().length).toEqual(0); + }); + + it('should flush all queued events using the old odpConfig when updateSettings is called()', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + + const odpConfig = new OdpConfig('old-key', 'old-host', 'https://new-odp.pixel.com/', []); + const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com/', []); + + const apiManager = instance(mockApiManager); + const eventManager = new TestOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 200, + flushInterval: 100, + }); + + eventManager.start(); + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + + expect(eventManager.getQueue().length).toEqual(25); + + eventManager.updateSettings(updatedConfig); + + jest.runAllTicks(); + + verify(mockApiManager.sendEvents(anything(), anything())).once(); + expect(eventManager.getQueue().length).toEqual(0); + const [usedOdpConfig] = capture(mockApiManager.sendEvents).last(); + expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); }); - it('should flush all scheduled events before stopping', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); + it('should use updated odpConfig to send events', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + + const odpConfig = new OdpConfig('old-key', 'old-host', 'https://new-odp.pixel.com/', []); + const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com/', []); + const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, - batchSize: 2, // batches of 2 with... + batchSize: 200, flushInterval: 100, }); eventManager.start(); - // ...25 events should... for (let i = 0; i < 25; i += 1) { eventManager.sendEvent(makeEvent(i)); } - await pause(300); - await eventManager.stop(); - verify(mockLogger.log(LogLevel.DEBUG, 'Stop requested.')).once(); - verify(mockLogger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', 0)).once(); + expect(eventManager.getQueue().length).toEqual(25); + + jest.advanceTimersByTime(100); + + expect(eventManager.getQueue().length).toEqual(0); + let [usedOdpConfig] = capture(mockApiManager.sendEvents).first(); + expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); + + eventManager.updateSettings(updatedConfig); + jest.runAllTicks(); + + + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + jest.advanceTimersByTime(100); + + expect(eventManager.getQueue().length).toEqual(0); + ([usedOdpConfig] = capture(mockApiManager.sendEvents).last()); + expect(usedOdpConfig.equals(updatedConfig)).toBeTruthy(); }); it('should prepare correct payload for register VUID', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -482,9 +635,10 @@ describe('OdpEventManager', () => { eventManager.start(); eventManager.registerVuid(vuid); - await pause(1500); - const [events] = capture(mockApiManager.sendEvents).last(); + jest.advanceTimersByTime(250); + + const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toBe(1); const [event] = events; @@ -498,12 +652,11 @@ describe('OdpEventManager', () => { }); it('should send correct event payload for identify user', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -518,9 +671,10 @@ describe('OdpEventManager', () => { eventManager.start(); eventManager.identifyUser(fsUserId, vuid); - await pause(1500); - const [events] = capture(mockApiManager.sendEvents).last(); + jest.advanceTimersByTime(250); + + const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toBe(1); const [event] = events; @@ -533,29 +687,6 @@ describe('OdpEventManager', () => { expect(event.data.get("data_source_version") as string).not.toBeNull(); }); - it('should apply updated ODP configuration when available', () => { - const eventManager = new OdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - const apiKey = 'testing-api-key'; - const apiHost = 'https://some.other.example.com/'; - const pixelUrl = 'https://some.other.pixel.com/'; - const segmentsToCheck = ['empty-cart', '1-item-cart']; - const differentOdpConfig = new OdpConfig(apiKey, apiHost, pixelUrl, segmentsToCheck); - - eventManager.updateSettings(differentOdpConfig); - - expect(eventManager['odpConfig'].apiKey).toEqual(apiKey); - expect(eventManager['odpConfig'].apiHost).toEqual(apiHost); - expect(eventManager['odpConfig'].pixelUrl).toEqual(pixelUrl); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[0]); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[1]); - }); - it('should error when no identifiers are provided in Node', () => { const eventManager = new NodeOdpEventManager({ odpConfig, @@ -570,6 +701,8 @@ describe('OdpEventManager', () => { eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); eventManager.stop(); + jest.runAllTicks(); + verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).twice(); }); @@ -587,6 +720,8 @@ describe('OdpEventManager', () => { eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); eventManager.stop(); + jest.runAllTicks(); + verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).never(); }); }); diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index ade9d48ce..b9ecb76f0 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import { BrowserOdpManager } from './../lib/plugins/odp_manager/index.browser'; import { IOdpEventManager, OdpOptions } from './../lib/shared_types'; import { OdpConfig } from '../lib/core/odp/odp_config'; import { BrowserOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.browser'; -import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.browser'; import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; @@ -35,6 +34,9 @@ import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; import { OdpEvent } from '../lib/core/odp/odp_event'; +import { LRUCache } from '../lib/utils/lru_cache'; +import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.browser'; +import { OdpManager } from '../lib/core/odp/odp_manager'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -80,7 +82,7 @@ describe('OdpManager', () => { mockLogger = mock<LogHandler>(); mockRequestHandler = mock<RequestHandler>(); - odpConfig = new OdpConfig(); + odpConfig = new OdpConfig(keyA, hostA, pixelA, segmentsA); fakeLogger = instance(mockLogger); fakeRequestHandler = instance(mockRequestHandler); @@ -106,181 +108,22 @@ describe('OdpManager', () => { }); const browserOdpManagerInstance = () => - new BrowserOdpManager({ + BrowserOdpManager.createInstance({ odpOptions: { eventManager: fakeEventManager, segmentManager: fakeSegmentManager, }, }); - it('should register VUID automatically on BrowserOdpManager initialization', async () => { + it('should create VUID automatically on BrowserOdpManager initialization', async () => { const browserOdpManager = browserOdpManagerInstance(); const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); expect(browserOdpManager.vuid).toBe(vuidManager.vuid); }); - it('should drop relevant calls when OdpManager is initialized with the disabled flag, except for VUID', async () => { - const browserOdpManager = new BrowserOdpManager({ logger: fakeLogger, odpOptions: { disabled: true } }); - - verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); - - browserOdpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); - expect(browserOdpManager.odpConfig).toBeUndefined; - - await browserOdpManager.fetchQualifiedSegments('vuid_user1', []); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); - - await browserOdpManager.identifyUser('vuid_user1'); - verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); - - expect(browserOdpManager.eventManager).toBeUndefined; - expect(browserOdpManager.segmentManager).toBeUndefined; - - const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); - expect(vuidManager.vuid.slice(0, 5)).toBe('vuid_'); - }); - - it('should start ODP Event Manager when ODP Manager is initialized', () => { - const browserOdpManager = browserOdpManagerInstance(); - verify(mockEventManager.start()).once(); - expect(browserOdpManager.eventManager).not.toBeUndefined(); - }); - - it('should stop ODP Event Manager when close is called', () => { - const browserOdpManager = browserOdpManagerInstance(); - verify(mockEventManager.stop()).never(); - - browserOdpManager.close(); - verify(mockEventManager.stop()).once(); - }); - - it('should use new settings in event manager when ODP Config is updated', async () => { - const browserOdpManager = new BrowserOdpManager({ - odpOptions: { - eventManager: fakeEventManager, - }, - }); - - expect(browserOdpManager.eventManager).toBeDefined(); - verify(mockEventManager.updateSettings(anything())).once(); - verify(mockEventManager.start()).once(); - - await new Promise(resolve => setTimeout(resolve, 200)); // Wait for VuidManager to fetch from cache. - - verify(mockEventManager.registerVuid(anything())).once(); - - const didUpdateA = browserOdpManager.updateSettings(odpConfigA); - expect(didUpdateA).toBe(true); - expect(browserOdpManager.odpConfig.equals(odpConfigA)).toBe(true); - - const updateSettingsArgsA = capture(mockEventManager.updateSettings).last(); - expect(updateSettingsArgsA[0]).toStrictEqual(odpConfigA); - - await browserOdpManager.identifyUser(userA); - const identifyUserArgsA = capture(mockEventManager.identifyUser).last(); - expect(identifyUserArgsA[0]).toStrictEqual(userA); - - const didUpdateB = browserOdpManager.updateSettings(odpConfigB); - expect(didUpdateB).toBe(true); - expect(browserOdpManager.odpConfig.equals(odpConfigB)).toBe(true); - - const updateSettingsArgsB = capture(mockEventManager.updateSettings).last(); - expect(updateSettingsArgsB[0]).toStrictEqual(odpConfigB); - - await browserOdpManager.eventManager!.identifyUser(userB); - const identifyUserArgsB = capture(mockEventManager.identifyUser).last(); - expect(identifyUserArgsB[0]).toStrictEqual(userB); - }); - - it('should use new settings in segment manager when ODP Config is updated', () => { - const browserOdpManager = new BrowserOdpManager({ - odpOptions: { - segmentManager: new OdpSegmentManager( - odpConfig, - new BrowserLRUCache<string, string[]>(), - fakeSegmentApiManager - ), - }, - }); - - const didUpdateA = browserOdpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); - expect(didUpdateA).toBe(true); - - browserOdpManager.fetchQualifiedSegments(vuidA); - - verify( - mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING) - ).never(); - - const fetchQualifiedSegmentsArgsA = capture(mockSegmentApiManager.fetchSegments).last(); - expect(fetchQualifiedSegmentsArgsA).toStrictEqual([keyA, hostA, ODP_USER_KEY.VUID, vuidA, segmentsA]); - - const didUpdateB = browserOdpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - expect(didUpdateB).toBe(true); - - browserOdpManager.fetchQualifiedSegments(vuidB); - - const fetchQualifiedSegmentsArgsB = capture(mockSegmentApiManager.fetchSegments).last(); - expect(fetchQualifiedSegmentsArgsB).toStrictEqual([keyB, hostB, ODP_USER_KEY.VUID, vuidB, segmentsB]); - }); - - it('should get event manager', () => { - const browserOdpManagerA = browserOdpManagerInstance(); - expect(browserOdpManagerA.eventManager).not.toBe(null); - - const browserOdpManagerB = new BrowserOdpManager({}); - expect(browserOdpManagerB.eventManager).not.toBe(null); - }); - - it('should get segment manager', () => { - const browserOdpManagerA = browserOdpManagerInstance(); - expect(browserOdpManagerA.segmentManager).not.toBe(null); - - const browserOdpManagerB = new BrowserOdpManager({}); - expect(browserOdpManagerB.eventManager).not.toBe(null); - }); - - // it("should call event manager's sendEvent if ODP Event is valid", async () => { - // const browserOdpManager = new BrowserOdpManager({ - // odpOptions: { - // eventManager: fakeEventManager, - // }, - // }); - - // const odpConfig = new OdpConfig('key', 'host', []); - - // browserOdpManager.updateSettings(odpConfig); - - // // Test Valid OdpEvent - calls event manager with valid OdpEvent object - // const validIdentifiers = new Map(); - // validIdentifiers.set('vuid', vuidA); - - // const validOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, validIdentifiers); - - // await browserOdpManager.sendEvent(validOdpEvent); - // verify(mockEventManager.sendEvent(anything())).once(); - - // // Test Invalid OdpEvents - logs error and short circuits - // // Does not include `vuid` in identifiers does not have a local this.vuid populated in BrowserOdpManager - // browserOdpManager.vuid = undefined; - // const invalidOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, undefined); - - // await expect(browserOdpManager.sendEvent(invalidOdpEvent)).rejects.toThrow( - // ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING - // ); - // }); - describe('Populates BrowserOdpManager correctly with all odpOptions', () => { - it('odpOptions.disabled = true disables BrowserOdpManager', () => { - const odpOptions: OdpOptions = { - disabled: true, - }; + beforeAll(() => { - const browserOdpManager = new BrowserOdpManager({ - odpOptions, - }); - - expect(browserOdpManager.enabled).toBe(false); }); it('Custom odpOptions.segmentsCache overrides default LRUCache', () => { @@ -289,17 +132,19 @@ describe('OdpManager', () => { maxSize: 2, timeout: 4000, }), - }; + }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); + const segmentManager = browserOdpManager['segmentManager'] as OdpSegmentManager; + // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(2); + expect(browserOdpManager.segmentManager._segmentsCache.maxSize).toBe(2); // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4000); + expect(browserOdpManager.segmentManager._segmentsCache.timeout).toBe(4000); }); it('Custom odpOptions.segmentsCacheSize overrides default LRUCache size', () => { @@ -307,12 +152,12 @@ describe('OdpManager', () => { segmentsCacheSize: 2, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(2); + expect(browserOdpManager.segmentManager._segmentsCache.maxSize).toBe(2); }); it('Custom odpOptions.segmentsCacheTimeout overrides default LRUCache timeout', () => { @@ -320,12 +165,12 @@ describe('OdpManager', () => { segmentsCacheTimeout: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4000); + expect(browserOdpManager.segmentManager._segmentsCache.timeout).toBe(4000); }); it('Custom odpOptions.segmentsApiTimeout overrides default Segment API Request Handler timeout', () => { @@ -333,7 +178,7 @@ describe('OdpManager', () => { segmentsApiTimeout: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -342,7 +187,7 @@ describe('OdpManager', () => { }); it('Browser default Segments API Request Handler timeout should be used when odpOptions does not include segmentsApiTimeout', () => { - const browserOdpManager = new BrowserOdpManager({}); + const browserOdpManager = BrowserOdpManager.createInstance({}); // @ts-ignore expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(10000); @@ -353,7 +198,7 @@ describe('OdpManager', () => { segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -367,7 +212,7 @@ describe('OdpManager', () => { segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 1), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -377,16 +222,17 @@ describe('OdpManager', () => { it('Custom odpOptions.segmentManager overrides default Segment Manager', () => { const customSegmentManager = new OdpSegmentManager( - odpConfig, new BrowserLRUCache<string, string[]>(), - fakeSegmentApiManager + fakeSegmentApiManager, + fakeLogger, + odpConfig, ); const odpOptions: OdpOptions = { segmentManager: customSegmentManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -396,12 +242,13 @@ describe('OdpManager', () => { it('Custom odpOptions.segmentManager override takes precedence over all other segments-related odpOptions', () => { const customSegmentManager = new OdpSegmentManager( - odpConfig, new BrowserLRUCache<string, string[]>({ maxSize: 1, timeout: 1, }), - new OdpSegmentApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger) + new OdpSegmentApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger), + fakeLogger, + odpConfig, ); const odpOptions: OdpOptions = { @@ -413,7 +260,7 @@ describe('OdpManager', () => { segmentManager: customSegmentManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -435,7 +282,7 @@ describe('OdpManager', () => { eventApiTimeout: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -446,7 +293,7 @@ describe('OdpManager', () => { it('Browser default Events API Request Handler timeout should be used when odpOptions does not include eventsApiTimeout', () => { const odpOptions: OdpOptions = {}; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -459,7 +306,7 @@ describe('OdpManager', () => { eventFlushInterval: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -470,7 +317,7 @@ describe('OdpManager', () => { it('Default ODP event flush interval is used when odpOptions does not include eventFlushInterval', () => { const odpOptions: OdpOptions = {}; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -483,7 +330,7 @@ describe('OdpManager', () => { eventFlushInterval: 0, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -499,7 +346,7 @@ describe('OdpManager', () => { eventBatchSize: 2, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -512,7 +359,7 @@ describe('OdpManager', () => { eventQueueSize: 2, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -525,7 +372,7 @@ describe('OdpManager', () => { eventRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -542,7 +389,7 @@ describe('OdpManager', () => { eventRequestHandler: new BrowserRequestHandler(fakeLogger, 1), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -566,7 +413,7 @@ describe('OdpManager', () => { eventManager: customEventManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -604,7 +451,7 @@ describe('OdpManager', () => { eventManager: customEventManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -642,7 +489,7 @@ describe('OdpManager', () => { eventQueueSize: 4, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 5d3b0e465..90228cc52 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ /// <reference types="jest" /> -import { anything, instance, mock, resetCalls, verify } from 'ts-mockito'; +import { anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LOG_MESSAGES } from './../lib/utils/enums/index'; import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; @@ -24,13 +24,16 @@ import { LogHandler, LogLevel } from '../lib/modules/logging'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; -import { NodeOdpManager as OdpManager } from './../lib/plugins/odp_manager/index.node'; -import { OdpConfig } from '../lib/core/odp/odp_config'; +import { OdpManager, Status } from '../lib/core/odp/odp_manager'; +import { OdpConfig, OdpIntegratedConfig, OdpIntegrationConfig, OdpNotIntegratedConfig } from '../lib/core/odp/odp_config'; import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; import { NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; -import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; +import { IOdpSegmentManager, OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; -import { ServerLRUCache } from '../lib/utils/lru_cache'; +import { IOdpEventManager } from '../lib/shared_types'; +import { wait } from './testUtils'; +import { resolvablePromise } from '../lib/utils/promise/resolvablePromise'; +import exp from 'constants'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -44,6 +47,40 @@ const pixelB = 'pixel-b'; const segmentsB = ['b']; const userB = 'fs-user-b'; +const testOdpManager = ({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled, + vuid, + vuidInitializer, +}: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + vuidEnabled?: boolean; + vuid?: string; + vuidInitializer?: () => Promise<void>; +}): OdpManager => { + class TestOdpManager extends OdpManager{ + constructor() { + super({ odpIntegrationConfig, segmentManager, eventManager, logger }); + } + isVuidEnabled(): boolean { + return vuidEnabled ?? false; + } + getVuid(): string { + return vuid ?? 'vuid_123'; + } + protected initializeVuid(): Promise<void> { + return vuidInitializer?.() ?? Promise.resolve(); + } + } + return new TestOdpManager(); +} + describe('OdpManager', () => { let mockLogger: LogHandler; let mockRequestHandler: RequestHandler; @@ -66,7 +103,6 @@ describe('OdpManager', () => { mockLogger = mock<LogHandler>(); mockRequestHandler = mock<RequestHandler>(); - odpConfig = new OdpConfig(); logger = instance(mockLogger); defaultRequestHandler = instance(mockRequestHandler); @@ -89,142 +125,577 @@ describe('OdpManager', () => { resetCalls(mockSegmentManager); }); - const odpManagerInstance = (config?: OdpConfig) => - new OdpManager({ - odpOptions: { - eventManager, - segmentManager, - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, + + it('should be in stopped status and not ready if constructed without odpIntegrationConfig', () => { + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, }); - it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { - const odpManager = new OdpManager({ + expect(odpManager.isReady()).toBe(false); + expect(odpManager.getStatus()).toEqual(Status.Stopped); + }); + + it('should call initialzeVuid on construction if vuid is enabled', () => { + const vuidInitializer = jest.fn(); + + const odpManager = testOdpManager({ + segmentManager, + eventManager, logger, - odpOptions: { - disabled: true, - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, + vuidEnabled: true, + vuidInitializer: vuidInitializer, }); - verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); - odpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); - expect(odpManager.odpConfig).toBeUndefined; + expect(vuidInitializer).toHaveBeenCalledTimes(1); + }); - await odpManager.fetchQualifiedSegments('user1', []); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + it('should become ready only after odpIntegrationConfig is provided if vuid is not enabled', async () => { + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: false, + }); - odpManager.identifyUser('user1'); - verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); + // should not be ready untill odpIntegrationConfig is provided + await wait(500); + expect(odpManager.isReady()).toBe(false); - expect(odpManager.eventManager).toBeUndefined; - expect(odpManager.segmentManager).toBeUndefined; + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); }); - it('should start ODP Event Manager when ODP Manager is initialized', () => { - const odpManager = odpManagerInstance(); - verify(mockEventManager.start()).once(); - expect(odpManager.eventManager).not.toBeUndefined(); + it('should become ready if odpIntegrationConfig is provided in constructor and then initialzeVuid', async () => { + const vuidPromise = resolvablePromise<void>(); + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + + const vuidInitializer = () => { + return vuidPromise.promise; + } + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + vuidInitializer, + }); + + await wait(500); + expect(odpManager.isReady()).toBe(false); + + vuidPromise.resolve(); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + }); + + it('should become ready after odpIntegrationConfig is provided using updateSettings() and then initialzeVuid finishes', async () => { + const vuidPromise = resolvablePromise<void>(); + + const vuidInitializer = () => { + return vuidPromise.promise; + } + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + vuidInitializer, + }); + + + expect(odpManager.isReady()).toBe(false); + + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await wait(500); + expect(odpManager.isReady()).toBe(false); + + vuidPromise.resolve(); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + }); + + it('should become ready after initialzeVuid finishes and then odpIntegrationConfig is provided using updateSettings()', async () => { + const vuidPromise = resolvablePromise<void>(); + + const vuidInitializer = () => { + return vuidPromise.promise; + } + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + vuidInitializer, + }); + + expect(odpManager.isReady()).toBe(false); + vuidPromise.resolve(); + + await wait(500); + expect(odpManager.isReady()).toBe(false); + + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); }); - it('should stop ODP Event Manager when close is called', () => { - const odpManager = odpManagerInstance(); + it('should become ready and stay in stopped state and not start eventManager if OdpNotIntegrated config is provided', async () => { + const vuidPromise = resolvablePromise<void>(); + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Stopped); + verify(mockEventManager.start()).never(); + }); + + it('should pass the integrated odp config given in constructor to eventManger and segmentManager', async () => { + when(mockEventManager.updateSettings(anything())).thenReturn(undefined); + when(mockSegmentManager.updateSettings(anything())).thenReturn(undefined); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + verify(mockEventManager.updateSettings(anything())).once(); + const [eventOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(eventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + + verify(mockSegmentManager.updateSettings(anything())).once(); + const [segmentOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(segmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + }); + + it('should pass the integrated odp config given in updateSettings() to eventManger and segmentManager', async () => { + when(mockEventManager.updateSettings(anything())).thenReturn(undefined); + when(mockSegmentManager.updateSettings(anything())).thenReturn(undefined); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + odpManager.updateSettings(odpIntegrationConfig); + + verify(mockEventManager.updateSettings(anything())).once(); + const [eventOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(eventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + + verify(mockSegmentManager.updateSettings(anything())).once(); + const [segmentOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(segmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + }); + + it('should start if odp is integrated and start odpEventManger', async () => { + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + odpManager.updateSettings(odpIntegrationConfig); + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Running); + }); + + it('should just update config when updateSettings is called in running state', async () => { + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Running); + + const newOdpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyB, hostB, pixelB, segmentsB) + }; + + odpManager.updateSettings(newOdpIntegrationConfig); + + verify(mockEventManager.start()).once(); verify(mockEventManager.stop()).never(); + verify(mockEventManager.updateSettings(anything())).twice(); + const [firstEventOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(firstEventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + const [secondEventOdpConfig] = capture(mockEventManager.updateSettings).second(); + expect(secondEventOdpConfig.equals(newOdpIntegrationConfig.odpConfig)).toBe(true); + + verify(mockSegmentManager.updateSettings(anything())).twice(); + const [firstSegmentOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(firstSegmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + const [secondSegmentOdpConfig] = capture(mockEventManager.updateSettings).second(); + expect(secondSegmentOdpConfig.equals(newOdpIntegrationConfig.odpConfig)).toBe(true); + }); - odpManager.close(); + it('should stop and stop eventManager if OdpNotIntegrated config is updated in running state', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Running); + + const newOdpIntegrationConfig: OdpNotIntegratedConfig = { + integrated: false, + }; + + odpManager.updateSettings(newOdpIntegrationConfig); + + expect(odpManager.getStatus()).toEqual(Status.Stopped); verify(mockEventManager.stop()).once(); }); - it('should use new settings in event manager when ODP Config is updated', async () => { - const odpManager = new OdpManager({ - odpOptions: { - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - eventManager: new OdpEventManager({ - odpConfig, - apiManager: eventApiManager, - logger, - clientEngine: '', - clientVersion: '', - batchSize: 1, - flushInterval: 250, - }), - }, + it('should register vuid after becoming ready if odp is integrated', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + verify(mockEventManager.registerVuid(anything())).once(); + }); + + it('should call eventManager.identifyUser with correct parameters when identifyUser is called', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + const userId = 'user123'; + const vuid = 'vuid_123'; + + odpManager.identifyUser(userId, vuid); + const [userIdArg, vuidArg] = capture(mockEventManager.identifyUser).byCallIndex(0); + expect(userIdArg).toEqual(userId); + expect(vuidArg).toEqual(vuid); + + odpManager.identifyUser(userId); + const [userIdArg2, vuidArg2] = capture(mockEventManager.identifyUser).byCallIndex(1); + expect(userIdArg2).toEqual(userId); + expect(vuidArg2).toEqual(undefined); + + odpManager.identifyUser(vuid); + const [userIdArg3, vuidArg3] = capture(mockEventManager.identifyUser).byCallIndex(2); + expect(userIdArg3).toEqual(undefined); + expect(vuidArg3).toEqual(vuid); + }); + + it('should send event with correct parameters', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', 'value1'], ['key2', 'value2']]); + + odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, + }); + + const [event] = capture(mockEventManager.sendEvent).byCallIndex(0); + expect(event.action).toEqual('action'); + expect(event.type).toEqual('type'); + expect(event.identifiers).toEqual(identifiers); + expect(event.data).toEqual(data); + + // should use `fullstack` as type if empty string is provided + odpManager.sendEvent({ + type: '', + action: 'action', + identifiers, + data, + }); + + const [event2] = capture(mockEventManager.sendEvent).byCallIndex(1); + expect(event2.action).toEqual('action'); + expect(event2.type).toEqual('fullstack'); + expect(event2.identifiers).toEqual(identifiers); + }); + + + it('should throw an error if event action is empty string and not call eventManager', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', 'value1'], ['key2', 'value2']]); + + const sendEvent = () => odpManager.sendEvent({ + action: '', + type: 'type', + identifiers, + data, }); - odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); + expect(sendEvent).toThrow('ODP action is not valid'); + verify(mockEventManager.sendEvent(anything())).never(); + }); - expect(odpManager.odpConfig.apiKey).toBe(keyA); - expect(odpManager.odpConfig.apiHost).toBe(hostA); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); + it('should throw an error if event data is invalid', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; - // odpManager.identifyUser(userA); + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); - // verify(mockEventApiManager.sendEvents(keyA, hostA, anything())).once(); + await odpManager.onReady(); - odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - expect(odpManager.odpConfig.apiKey).toBe(keyB); - expect(odpManager.odpConfig.apiHost).toBe(hostB); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', {}]]); - // odpManager.identifyUser(userB); + const sendEvent = () => odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, + }); - // verify(mockEventApiManager.sendEvents(keyB, hostB, anything())).once(); + expect(sendEvent).toThrow(ERROR_MESSAGES.ODP_INVALID_DATA); + verify(mockEventManager.sendEvent(anything())).never(); }); - it('should use new settings in segment manager when ODP Config is updated', async () => { - const odpManager = new OdpManager({ - odpOptions: { - segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache<string, string[]>(), segmentApiManager), - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, + it.only('should fetch qualified segments correctly for both fs_user_id and vuid', async () => { + const userId = 'user123'; + const vuid = 'vuid_123'; + + when(mockSegmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, anything())) + .thenResolve(['fs1', 'fs2']); + + when(mockSegmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, vuid, anything())) + .thenResolve(['vuid1', 'vuid2']); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager: instance(mockSegmentManager), + eventManager, + logger, + vuidEnabled: true, }); - odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); + await odpManager.onReady(); - expect(odpManager.odpConfig.apiKey).toBe(keyA); - expect(odpManager.odpConfig.apiHost).toBe(hostA); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); + const fsSegments = await odpManager.fetchQualifiedSegments(userId); + expect(fsSegments).toEqual(['fs1', 'fs2']); + + const vuidSegments = await odpManager.fetchQualifiedSegments(vuid); + expect(vuidSegments).toEqual(['vuid1', 'vuid2']); + }); + + + it('should stop itself and eventManager if stop is called', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); - await odpManager.fetchQualifiedSegments(userA); - verify(mockSegmentApiManager.fetchSegments(keyA, hostA, ODP_USER_KEY.FS_USER_ID, userA, anything())).once(); + await odpManager.onReady(); - odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - expect(odpManager.odpConfig.apiKey).toBe(keyB); - expect(odpManager.odpConfig.apiHost).toBe(hostB); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); + odpManager.stop(); - await odpManager.fetchQualifiedSegments(userB); - verify(mockSegmentApiManager.fetchSegments(keyB, hostB, ODP_USER_KEY.FS_USER_ID, userB, anything())).once(); + expect(odpManager.getStatus()).toEqual(Status.Stopped); + verify(mockEventManager.stop()).once(); }); - it('should get event manager', () => { - const odpManagerA = odpManagerInstance(); - expect(odpManagerA.eventManager).not.toBe(null); - const odpManagerB = new OdpManager({ + + it('should drop relevant calls and log error when odpIntegrationConfig is not available', async () => { + const odpManager = testOdpManager({ + odpIntegrationConfig: undefined, + segmentManager, + eventManager, logger, - odpOptions: { - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, + vuidEnabled: true, }); - expect(odpManagerB.eventManager).not.toBe(null); + + const segments = await odpManager.fetchQualifiedSegments('vuid_user1', []); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).once(); + expect(segments).toBeNull(); + + odpManager.identifyUser('vuid_user1'); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).twice(); + verify(mockEventManager.identifyUser(anything(), anything())).never(); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', {}]]); + + odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, + }); + + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).thrice(); + verify(mockEventManager.sendEvent(anything())).never(); + }); - it('should get segment manager', () => { - const odpManagerA = odpManagerInstance(); - expect(odpManagerA.segmentManager).not.toBe(null); + it('should drop relevant calls and log error when odp is not integrated', async () => { + const odpManager = testOdpManager({ + odpIntegrationConfig: { integrated: false }, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + const segments = await odpManager.fetchQualifiedSegments('vuid_user1', []); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).once(); + expect(segments).toBeNull(); - const odpManagerB = new OdpManager({ - odpOptions: { - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, + odpManager.identifyUser('vuid_user1'); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).twice(); + verify(mockEventManager.identifyUser(anything(), anything())).never(); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', {}]]); + + odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, }); - expect(odpManagerB.eventManager).not.toBe(null); + + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).thrice(); + verify(mockEventManager.sendEvent(anything())).never(); }); }); + diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts index 5deea4348..f4421175f 100644 --- a/tests/odpSegmentManager.spec.ts +++ b/tests/odpSegmentManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,8 +45,6 @@ describe('OdpSegmentManager', () => { const mockLogHandler = mock<LogHandler>(); const mockRequestHandler = mock<RequestHandler>(); - let manager: OdpSegmentManager; - let odpConfig: OdpConfig; const apiManager = new MockOdpSegmentApiManager(instance(mockRequestHandler), instance(mockLogHandler)); let options: Array<OptimizelySegmentOption> = []; @@ -57,6 +55,13 @@ describe('OdpSegmentManager', () => { const validTestOdpConfig = new OdpConfig('valid-key', 'host', 'pixel-url', ['new-customer']); const invalidTestOdpConfig = new OdpConfig('invalid-key', 'host', 'pixel-url', ['new-customer']); + const getSegmentsCache = () => { + return new LRUCache<string, string[]>({ + maxSize: 1000, + timeout: 1000, + }); + } + beforeEach(() => { resetCalls(mockLogHandler); resetCalls(mockRequestHandler); @@ -64,93 +69,100 @@ describe('OdpSegmentManager', () => { const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; const PIXEL_URL = 'https://odp.pixel.com/'; - odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); - const segmentsCache = new LRUCache<string, string[]>({ - maxSize: 1000, - timeout: 1000, - }); - - manager = new OdpSegmentManager(odpConfig, segmentsCache, apiManager); }); it('should fetch segments successfully on cache miss.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, '123', ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, '123', ['a']); const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); }); it('should fetch segments successfully on cache hit.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['a']); }); - it('should throw an error when fetching segments returns an error.', async () => { - odpConfig.update(invalidTestOdpConfig); + it('should return null when fetching segments returns an error.', async () => { + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, invalidTestOdpConfig); const segments = await manager.fetchQualifiedSegments(userKey, userValue, []); expect(segments).toBeNull; }); it('should ignore the cache if the option enum is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); options = [OptimizelySegmentOption.IGNORE_CACHE]; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(cacheCount()).toBe(1); + expect(cacheCount(manager)).toBe(1); }); it('should ignore the cache if the option string is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager,userKey, userValue, ['a']); // @ts-ignore options = ['IGNORE_CACHE']; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(cacheCount()).toBe(1); + expect(cacheCount(manager)).toBe(1); }); it('should reset the cache if the option enum is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); - setCache(userKey, '123', ['a']); - setCache(userKey, '456', ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); + setCache(manager, userKey, '123', ['a']); + setCache(manager, userKey, '456', ['a']); options = [OptimizelySegmentOption.RESET_CACHE]; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(peekCache(userKey, userValue)).toEqual(segments); - expect(cacheCount()).toBe(1); + expect(peekCache(manager, userKey, userValue)).toEqual(segments); + expect(cacheCount(manager)).toBe(1); + }); + + it('should reset the cache on settings update.', async () => { + const oldConfig = new OdpConfig('old-key', 'old-host', 'pixel-url', ['new-customer']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + + setCache(manager, userKey, userValue, ['a']); + expect(cacheCount(manager)).toBe(1); + + const newConfig = new OdpConfig('new-key', 'new-host', 'pixel-url', ['new-customer']); + manager.updateSettings(newConfig); + + expect(cacheCount(manager)).toBe(0); }); it('should reset the cache if the option string is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); - setCache(userKey, '123', ['a']); - setCache(userKey, '456', ['a']); - // @ts-ignore + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); + setCache(manager, userKey, '123', ['a']); + setCache(manager, userKey, '456', ['a']); + // @ts-ignore options = ['RESET_CACHE']; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(peekCache(userKey, userValue)).toEqual(segments); - expect(cacheCount()).toBe(1); + expect(peekCache(manager, userKey, userValue)).toEqual(segments); + expect(cacheCount(manager)).toBe(1); }); it('should make a valid cache key.', () => { + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); expect('vuid-$-test-user').toBe(manager.makeCacheKey(userKey, userValue)); }); // Utility Functions - function setCache(userKey: string, userValue: string, value: string[]) { + function setCache(manager: OdpSegmentManager, userKey: string, userValue: string, value: string[]) { const cacheKey = manager.makeCacheKey(userKey, userValue); manager.segmentsCache.save({ key: cacheKey, @@ -158,10 +170,10 @@ describe('OdpSegmentManager', () => { }); } - function peekCache(userKey: string, userValue: string): string[] | null { + function peekCache(manager: OdpSegmentManager, userKey: string, userValue: string): string[] | null { const cacheKey = manager.makeCacheKey(userKey, userValue); return (manager.segmentsCache as LRUCache<string, string[]>).peek(cacheKey); } - const cacheCount = () => (manager.segmentsCache as LRUCache<string, string[]>).map.size; + const cacheCount = (manager: OdpSegmentManager) => (manager.segmentsCache as LRUCache<string, string[]>).map.size; }); diff --git a/tests/testUtils.ts b/tests/testUtils.ts index 2c28c259b..2af292e09 100644 --- a/tests/testUtils.ts +++ b/tests/testUtils.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,6 @@ export const getTestPersistentCache = (): PersistentKeyValueCache => { }), set: jest.fn().mockImplementation((): Promise<void> => { - console.log('mock set called'); return Promise.resolve(); }), @@ -57,3 +56,7 @@ export const getTestPersistentCache = (): PersistentKeyValueCache => { return cache; } + +export const wait = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; From 75418da42a93dd94e63885685d3c55605fdaf6d4 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Mon, 8 Apr 2024 08:51:35 -0400 Subject: [PATCH 075/200] [FSSDK-10090] Prepare for v5.3.0 release (#921) * build: bump semantic version * docs: update changelog --------- Co-authored-by: Mike Chu <michael.chu@optmizely.com> --- CHANGELOG.md | 10 +++++++++- lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e669ca75..6a2fccd58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [5.3.0] - April 8, 2024 + +### Changed +- Refactor: ODP corrections [#920](https://github.com/optimizely/javascript-sdk/pull/920) including + - ODPManager should not be running and scheduling timer if ODP is not integrated to the project (which causes memory leak if one sdk instance is created per request) + - CreateUserContext should work even when called before the datafile is downloaded and should send the `identify` ODP events after datafile download completes + - Other automatic odp events (vuid registration, client initialized) should also be sent after datafile is available and should not be dropped if batching is disabled. + - [see PR for more] + ## [5.2.1] - March 25, 2024 diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index e9b5283e5..2fec99f18 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.2.1'); + assert.equal(optlyInstance.clientVersion, '5.3.0'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 44da77f11..d28c953c1 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.2.1'); + assert.equal(optlyInstance.clientVersion, '5.3.0'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 0ceed3057..75d23963c 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.2.1'); + assert.equal(optlyInstance.clientVersion, '5.3.0'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 3f7bee937..58691d8c4 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -221,7 +221,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.2.1'; +export const CLIENT_VERSION = '5.3.0'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 0e90154c4..9b20baf7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.2.1", + "version": "5.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.2.1", + "version": "5.3.0", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index cd15db28b..a668acc95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.2.1", + "version": "5.3.0", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index afc59cc56..eed2fe69c 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.2.1'); + expect(optlyInstance.clientVersion).toEqual('5.3.0'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 34496f289b9510a16b4c08ec3f9156e2227ec00e Mon Sep 17 00:00:00 2001 From: pulak-opti <129880418+pulak-opti@users.noreply.github.com> Date: Mon, 13 May 2024 21:33:52 +0600 Subject: [PATCH 076/200] [FSSDK-10180] close http request after getting response to release memory immediately (node) (#927) * imediately close req and release memory after http call (node) * handle any status code & error case --- lib/plugins/event_dispatcher/index.node.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/plugins/event_dispatcher/index.node.ts b/lib/plugins/event_dispatcher/index.node.ts index f9f716caf..8efd7fb4f 100644 --- a/lib/plugins/event_dispatcher/index.node.ts +++ b/lib/plugins/event_dispatcher/index.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018, 2020-2021, Optimizely + * Copyright 2016-2018, 2020-2021, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,19 +51,26 @@ export const dispatchEvent = function( }, }; + const reqWrapper: { req?: http.ClientRequest } = {}; + const requestCallback = function(response?: { statusCode: number }): void { if (response && response.statusCode && response.statusCode >= 200 && response.statusCode < 400) { callback(response); } + reqWrapper.req?.destroy(); + reqWrapper.req = undefined; }; - const req = (parsedUrl.protocol === 'http:' ? http : https) + reqWrapper.req = (parsedUrl.protocol === 'http:' ? http : https) .request(requestOptions, requestCallback as (res: http.IncomingMessage) => void); // Add no-op error listener to prevent this from throwing - req.on('error', function() {}); - req.write(dataString); - req.end(); - return req; + reqWrapper.req.on('error', function() { + reqWrapper.req?.destroy(); + reqWrapper.req = undefined; + }); + reqWrapper.req.write(dataString); + reqWrapper.req.end(); + return reqWrapper.req; }; export default { From 2f6dc05c6aa7aacc8f6e2e99c1da128fc585742a Mon Sep 17 00:00:00 2001 From: pulak-opti <129880418+pulak-opti@users.noreply.github.com> Date: Tue, 14 May 2024 08:45:37 +0600 Subject: [PATCH 077/200] [FSSDK-10180] prepare for release 5.3.1-rc.1 (#928) --- CHANGELOG.md | 5 +++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a2fccd58..677958ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.3.1-rc.1] - May 13, 2024 + +### Changed +- Fix Memory Leak: Closed http request after getting response to release memory immediately (node) ([#927](https://github.com/optimizely/javascript-sdk/pull/927)) + ## [5.3.0] - April 8, 2024 ### Changed diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 2fec99f18..57d74139c 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.0'); + assert.equal(optlyInstance.clientVersion, '5.3.1-rc.1'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index d28c953c1..d90f1c684 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.0'); + assert.equal(optlyInstance.clientVersion, '5.3.1-rc.1'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 75d23963c..c7105c47c 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.0'); + assert.equal(optlyInstance.clientVersion, '5.3.1-rc.1'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 58691d8c4..4ef86b0ef 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -221,7 +221,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.3.0'; +export const CLIENT_VERSION = '5.3.1-rc.1'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 9b20baf7d..9e0dc6694 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.0", + "version": "5.3.1-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.3.0", + "version": "5.3.1-rc.1", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index a668acc95..34d10eb8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.0", + "version": "5.3.1-rc.1", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index eed2fe69c..95013c06a 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.3.0'); + expect(optlyInstance.clientVersion).toEqual('5.3.1-rc.1'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 43fea02411b583051f8b38e232d474bfba9f283b Mon Sep 17 00:00:00 2001 From: pulak-opti <129880418+pulak-opti@users.noreply.github.com> Date: Mon, 20 May 2024 20:38:18 +0600 Subject: [PATCH 078/200] [FSSDK-10180] prepare for release 5.3.1 (#931) --- CHANGELOG.md | 5 +++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 677958ce0..2379ffb20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.3.1] - May 20, 2024 + +### Changed +- Fix Memory Leak: Closed http request after getting response to release memory immediately (node) ([#927](https://github.com/optimizely/javascript-sdk/pull/927)) + ## [5.3.1-rc.1] - May 13, 2024 ### Changed diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 57d74139c..e3e98b357 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.1-rc.1'); + assert.equal(optlyInstance.clientVersion, '5.3.1'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index d90f1c684..99ebdbd11 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.1-rc.1'); + assert.equal(optlyInstance.clientVersion, '5.3.1'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index c7105c47c..54babaeec 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.1-rc.1'); + assert.equal(optlyInstance.clientVersion, '5.3.1'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 4ef86b0ef..faadb97a8 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -221,7 +221,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.3.1-rc.1'; +export const CLIENT_VERSION = '5.3.1'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 9e0dc6694..bd14b1fdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.1-rc.1", + "version": "5.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.3.1-rc.1", + "version": "5.3.1", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index 34d10eb8d..93c2ffa9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.1-rc.1", + "version": "5.3.1", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 95013c06a..5d5de27a3 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.3.1-rc.1'); + expect(optlyInstance.clientVersion).toEqual('5.3.1'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 01b50a2c50da24258105225d4802b09d7b704415 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 20 May 2024 22:06:53 +0600 Subject: [PATCH 079/200] [FSSDK-10198] making isOdpIntegrated public (#930) * odp not integrated log level change from error to info * odp not integrated log level back to prev state, except identifyUser * making isOdpIntegrated public * Adding JSDoc to newly exposed isODPIntegrated method * registerVuid method ODP integration log change from error to info * Update lib/optimizely/index.ts Co-authored-by: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> --------- Co-authored-by: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> --- lib/core/odp/odp_manager.ts | 17 ++++++++--------- lib/optimizely/index.ts | 19 +++++++++---------- lib/shared_types.ts | 8 +++++++- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 4da2f6191..54278358d 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -65,7 +65,6 @@ export abstract class OdpManager implements IOdpManager { * Promise that returns when the OdpManager is finished initializing */ private initPromise: Promise<unknown>; - private ready = false; /** @@ -126,7 +125,7 @@ export abstract class OdpManager implements IOdpManager { this.onReady().then(() => { this.ready = true; - if(this.isVuidEnabled() && this.status === Status.Running) { + if (this.isVuidEnabled() && this.status === Status.Running) { this.registerVuid(); } }); @@ -146,7 +145,7 @@ export abstract class OdpManager implements IOdpManager { } if (!this.odpIntegrationConfig) { - return Promise.reject(new Error('cannot start without ODP config')); + return Promise.reject(new Error('cannot start without ODP config')); } if (!this.odpIntegrationConfig.integrated) { @@ -211,13 +210,13 @@ export abstract class OdpManager implements IOdpManager { * @returns {Promise<string[] | null>} A promise holding either a list of qualified segments or null. */ async fetchQualifiedSegments(userId: string, options: Array<OptimizelySegmentOption> = []): Promise<string[] | null> { - if (!this.odpIntegrationConfig) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return null; } if (!this.odpIntegrationConfig.integrated) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return null; } @@ -225,7 +224,7 @@ export abstract class OdpManager implements IOdpManager { return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, userId, options); } - return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); + return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); } /** @@ -241,7 +240,7 @@ export abstract class OdpManager implements IOdpManager { } if (!this.odpIntegrationConfig.integrated) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + this.logger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return; } @@ -306,7 +305,7 @@ export abstract class OdpManager implements IOdpManager { } if (!this.odpIntegrationConfig.integrated) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + this.logger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return; } diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index e29d04daa..2a3eb5a0d 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -188,9 +188,8 @@ export default class Optimizely implements Client { this.nextReadyTimeoutId = 0; } - /** - * Returns the project configuration retrieved from projectConfigManager + * Returns the project configuration retrieved from projectConfigManager * @return {projectConfig.ProjectConfig} */ getProjectConfig(): projectConfig.ProjectConfig | null { @@ -1444,10 +1443,7 @@ export default class Optimizely implements Client { createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { const userIdentifier = userId ?? this.odpManager?.getVuid(); - if ( - userIdentifier === undefined || - !this.validateInputs({ user_id: userIdentifier }, attributes) - ) { + if (userIdentifier === undefined || !this.validateInputs({ user_id: userIdentifier }, attributes)) { return null; } @@ -1671,7 +1667,7 @@ export default class Optimizely implements Client { } if (this.odpManager) { - this.odpManager.updateSettings(projectConfig.odpIntegrationConfig) + this.odpManager.updateSettings(projectConfig.odpIntegrationConfig); } } @@ -1722,8 +1718,11 @@ export default class Optimizely implements Client { this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED, e); } } - - private isOdpIntegrated(): boolean { + /** + * Checks if ODP (Optimizely Data Platform) is integrated into the project. + * @returns { boolean } `true` if ODP settings were found in the datafile otherwise `false` + */ + public isOdpIntegrated(): boolean { return this.projectConfigManager.getConfig()?.odpIntegrationConfig?.integrated ?? false; } @@ -1751,7 +1750,7 @@ export default class Optimizely implements Client { if (!this.odpManager) { return null; } - + return await this.odpManager.fetchQualifiedSegments(userId, options); } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 361f293d5..08291ecf2 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -335,7 +335,12 @@ export interface Client { getForcedVariation(experimentKey: string, userId: string): string | null; isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean; getEnabledFeatures(userId: string, attributes?: UserAttributes): string[]; - getFeatureVariable(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): FeatureVariableValue; + getFeatureVariable( + featureKey: string, + variableKey: string, + userId: string, + attributes?: UserAttributes + ): FeatureVariableValue; getFeatureVariableBoolean( featureKey: string, variableKey: string, @@ -371,6 +376,7 @@ export interface Client { close(): Promise<{ success: boolean; reason?: string }>; sendOdpEvent(action: string, type?: string, identifiers?: Map<string, string>, data?: Map<string, unknown>): void; getProjectConfig(): ProjectConfig | null; + isOdpIntegrated(): boolean; } export interface ActivateListenerPayload extends ListenerPayload { From 851b06622fa6a0239500b3b65e2d3937334960de Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 21 May 2024 19:25:07 +0600 Subject: [PATCH 080/200] [FSSDK-10198]prepare for release 5.3.2 (#932) * prepare for version 5.3.2 * Revert "prepare for version 5.3.2" This reverts commit 345d59789568533d43072f155bfecc35eff45167. * prepare for release 5.3.2 --- CHANGELOG.md | 7 +++++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2379ffb20..d8db9cd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.3.2] - May 20, 2024 + +### Changed + +- Added public facing API for ODP integration information ([#930](https://github.com/optimizely/javascript-sdk/pull/930)) + + ## [5.3.1] - May 20, 2024 ### Changed diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index e3e98b357..fcd481b86 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.1'); + assert.equal(optlyInstance.clientVersion, '5.3.2'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 99ebdbd11..eecf67e32 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.1'); + assert.equal(optlyInstance.clientVersion, '5.3.2'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 54babaeec..aff679fb7 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.1'); + assert.equal(optlyInstance.clientVersion, '5.3.2'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index faadb97a8..5f0581b92 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -221,7 +221,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.3.1'; +export const CLIENT_VERSION = '5.3.2'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index bd14b1fdd..bae861e76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.1", + "version": "5.3.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.3.1", + "version": "5.3.2", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index 93c2ffa9e..729241fbe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.1", + "version": "5.3.2", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 5d5de27a3..ce9cb2e81 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.3.1'); + expect(optlyInstance.clientVersion).toEqual('5.3.2'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From c9972e63b3e9bf32b821add9ef2aaa8076e04b8e Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:18:28 +0600 Subject: [PATCH 081/200] [FSSDK-10201] queueMicrotask alternative for unsupported platforms (#933) * [FSSDK-10201] queueMicrotask alternative for unsupported platforms * [FSSDK-10201] dir rename, testcase addition * [FSSDK-10201] test improvement, redundant code removal * [FSSDK-10201] licence text addition, only removal * [FSSDK-10201] unused import removal --- lib/core/odp/odp_event_manager.ts | 5 ++- .../project_config/project_config_manager.ts | 6 +-- lib/utils/microtask/index.tests.js | 38 +++++++++++++++++++ lib/utils/microtask/index.ts | 25 ++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 lib/utils/microtask/index.tests.js create mode 100644 lib/utils/microtask/index.ts diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 80baa4822..3b91d7712 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -24,6 +24,7 @@ import { OdpConfig } from './odp_config'; import { IOdpEventApiManager } from './odp_event_api_manager'; import { invalidOdpDataFound } from './odp_utils'; import { IUserAgentParser } from './user_agent_parser'; +import { scheduleMicrotaskOrTimeout } from '../../utils/microtask'; const MAX_RETRIES = 3; @@ -393,14 +394,14 @@ export abstract class OdpEventManager implements IOdpEventManager { if (batch.length > 0) { // put sending the event on another event loop - queueMicrotask(async () => { + scheduleMicrotaskOrTimeout(async () => { let shouldRetry: boolean; let attemptNumber = 0; do { shouldRetry = await this.apiManager.sendEvents(odpConfig, batch); attemptNumber += 1; } while (shouldRetry && attemptNumber < this.retries); - }); + }) } } diff --git a/lib/core/project_config/project_config_manager.ts b/lib/core/project_config/project_config_manager.ts index 3f3aea4df..b0fe25ddd 100644 --- a/lib/core/project_config/project_config_manager.ts +++ b/lib/core/project_config/project_config_manager.ts @@ -20,6 +20,7 @@ import { ERROR_MESSAGES } from '../../utils/enums'; import { createOptimizelyConfig } from '../optimizely_config'; import { OnReadyResult, OptimizelyConfig, DatafileManager } from '../../shared_types'; import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from '../project_config'; +import { scheduleMicrotaskOrTimeout } from '../../utils/microtask'; const logger = getLogger(); const MODULE_NAME = 'PROJECT_CONFIG_MANAGER'; @@ -189,10 +190,9 @@ export class ProjectConfigManager { if (configObj && oldRevision !== configObj.revision) { this.configObj = configObj; this.optimizelyConfigObj = null; - - queueMicrotask(() => { + scheduleMicrotaskOrTimeout(() => { this.updateListeners.forEach(listener => listener(configObj)); - }); + }) } } diff --git a/lib/utils/microtask/index.tests.js b/lib/utils/microtask/index.tests.js new file mode 100644 index 000000000..16091ad68 --- /dev/null +++ b/lib/utils/microtask/index.tests.js @@ -0,0 +1,38 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { scheduleMicrotaskOrTimeout } from './'; + +describe('scheduleMicrotaskOrTimeout', () => { + it('should use queueMicrotask if available', (done) => { + // Assuming queueMicrotask is available in the environment + scheduleMicrotaskOrTimeout(() => { + done(); + }); + }); + + it('should fallback to setTimeout if queueMicrotask is not available', (done) => { + // Temporarily remove queueMicrotask to test the fallback + const originalQueueMicrotask = window.queueMicrotask; + window.queueMicrotask = undefined; + + scheduleMicrotaskOrTimeout(() => { + // Restore queueMicrotask before calling done + window.queueMicrotask = originalQueueMicrotask; + done(); + }); + }); +}); diff --git a/lib/utils/microtask/index.ts b/lib/utils/microtask/index.ts new file mode 100644 index 000000000..816b17a27 --- /dev/null +++ b/lib/utils/microtask/index.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type Callback = () => void; + +export const scheduleMicrotaskOrTimeout = (callback: Callback): void =>{ + if (typeof queueMicrotask === 'function') { + queueMicrotask(callback); + } else { + setTimeout(callback); + } +} \ No newline at end of file From 4909efb74335b754592ddf6e8415047fc0bf3e7b Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 6 Jun 2024 20:54:52 +0600 Subject: [PATCH 082/200] [FSSDK-10201] prepare for release (#934) --- CHANGELOG.md | 7 +++++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8db9cd19..31c9c5d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [5.3.3] - Jun 06, 2024 + +### Changed + +- queueMicroTask fallback addition for embedded environments / unsupported platforms ([#933](https://github.com/optimizely/javascript-sdk/pull/933)) + ## [5.3.2] - May 20, 2024 ### Changed diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index fcd481b86..7342f3f7b 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.2'); + assert.equal(optlyInstance.clientVersion, '5.3.3'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index eecf67e32..d9301202e 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.2'); + assert.equal(optlyInstance.clientVersion, '5.3.3'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index aff679fb7..c12efba91 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.2'); + assert.equal(optlyInstance.clientVersion, '5.3.3'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 5f0581b92..c415721c5 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -221,7 +221,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.3.2'; +export const CLIENT_VERSION = '5.3.3'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index bae861e76..23b1e8d71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.2", + "version": "5.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.3.2", + "version": "5.3.3", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index 729241fbe..f4c78cedd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.2", + "version": "5.3.3", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index ce9cb2e81..74bac7780 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -90,7 +90,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.3.2'); + expect(optlyInstance.clientVersion).toEqual('5.3.3'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 46e2ab48ae91f69b690ee01329de341347dce923 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:08:29 +0600 Subject: [PATCH 083/200] [FSSDK-10316] crypto and text encoder polyfill addition for React native (#936) * [FSSDK-10316] crypto and text encoder polyfill addition for React native * dev dep update for test cases * dependency version fix * format fix --- __mocks__/fast-text-encoding.ts | 1 + __mocks__/react-native-get-random-values.ts | 1 + lib/index.react_native.ts | 3 +++ package-lock.json | 25 ++++++++++++++------- package.json | 12 ++++++++-- tests/index.react_native.spec.ts | 21 +++++++++-------- 6 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 __mocks__/fast-text-encoding.ts create mode 100644 __mocks__/react-native-get-random-values.ts diff --git a/__mocks__/fast-text-encoding.ts b/__mocks__/fast-text-encoding.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/__mocks__/fast-text-encoding.ts @@ -0,0 +1 @@ +export {}; diff --git a/__mocks__/react-native-get-random-values.ts b/__mocks__/react-native-get-random-values.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/__mocks__/react-native-get-random-values.ts @@ -0,0 +1 @@ +export {}; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 9457052f6..ee5a1975c 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -29,6 +29,9 @@ import { createHttpPollingDatafileManager } from './plugins/datafile_manager/rea import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import * as commonExports from './common_exports'; +import 'fast-text-encoding'; +import 'react-native-get-random-values'; + const logger = getLogger(); setLogHandler(loggerPlugin.createLogger()); setLogLevel(LogLevel.INFO); diff --git a/package-lock.json b/package-lock.json index 23b1e8d71..223da7e68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "^5.9.10", + "@react-native-community/netinfo": "^11.3.2", "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/chai": "^4.2.11", @@ -70,7 +70,9 @@ "peerDependencies": { "@babel/runtime": "^7.0.0", "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "5.9.4" + "@react-native-community/netinfo": "^11.3.2", + "fast-text-encoding": "^1.0.6", + "react-native-get-random-values": "^1.11.0" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { @@ -78,6 +80,12 @@ }, "@react-native-community/netinfo": { "optional": true + }, + "fast-text-encoding": { + "optional": true + }, + "react-native-get-random-values": { + "optional": true } } }, @@ -3921,10 +3929,11 @@ } }, "node_modules/@react-native-community/netinfo": { - "version": "5.9.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", - "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz", + "integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==", "dev": true, + "license": "MIT", "peerDependencies": { "react-native": ">=0.59" } @@ -17999,9 +18008,9 @@ } }, "@react-native-community/netinfo": { - "version": "5.9.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", - "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz", + "integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index f4c78cedd..d2ec8c05b 100644 --- a/package.json +++ b/package.json @@ -111,8 +111,8 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@react-native-community/netinfo": "^11.3.2", "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "^5.9.10", "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/chai": "^4.2.11", @@ -162,7 +162,9 @@ "peerDependencies": { "@babel/runtime": "^7.0.0", "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "5.9.4" + "@react-native-community/netinfo": "^11.3.2", + "react-native-get-random-values": "^1.11.0", + "fast-text-encoding": "^1.0.6" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { @@ -170,6 +172,12 @@ }, "@react-native-community/netinfo": { "optional": true + }, + "react-native-get-random-values": { + "optional": true + }, + "fast-text-encoding": { + "optional": true } }, "publishConfig": { diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 74bac7780..32af4e94d 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -24,6 +24,9 @@ import optimizelyFactory from '../lib/index.react_native'; import configValidator from '../lib/utils/config_validator'; import eventProcessorConfigValidator from '../lib/utils/event_processor_config_validator'; +jest.mock('react-native-get-random-values') +jest.mock('fast-text-encoding') + describe('javascript-sdk/react-native', () => { beforeEach(() => { jest.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent'); @@ -45,10 +48,10 @@ describe('javascript-sdk/react-native', () => { }); describe('createInstance', () => { - var fakeErrorHandler = { handleError: function() {} }; - var fakeEventDispatcher = { dispatchEvent: function() {} }; + const fakeErrorHandler = { handleError: function() {} }; + const fakeEventDispatcher = { dispatchEvent: function() {} }; // @ts-ignore - var silentLogger; + let silentLogger; beforeEach(() => { // @ts-ignore @@ -65,7 +68,7 @@ describe('javascript-sdk/react-native', () => { it('should not throw if the provided config is not valid', () => { expect(function() { - var optlyInstance = optimizelyFactory.createInstance({ + const optlyInstance = optimizelyFactory.createInstance({ datafile: {}, // @ts-ignore logger: silentLogger, @@ -77,7 +80,7 @@ describe('javascript-sdk/react-native', () => { }); it('should create an instance of optimizely', () => { - var optlyInstance = optimizelyFactory.createInstance({ + const optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, @@ -94,7 +97,7 @@ describe('javascript-sdk/react-native', () => { }); it('should set the React Native JS client engine and javascript SDK version', () => { - var optlyInstance = optimizelyFactory.createInstance({ + const optlyInstance = optimizelyFactory.createInstance({ datafile: {}, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, @@ -111,7 +114,7 @@ describe('javascript-sdk/react-native', () => { }); it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { - var optlyInstance = optimizelyFactory.createInstance({ + const optlyInstance = optimizelyFactory.createInstance({ clientEngine: 'react-sdk', datafile: {}, errorHandler: fakeErrorHandler, @@ -155,7 +158,7 @@ describe('javascript-sdk/react-native', () => { }); it('should call logging.setLogHandler with the supplied logger', () => { - var fakeLogger = { log: function() {} }; + const fakeLogger = { log: function() {} }; optimizelyFactory.createInstance({ datafile: testData.getTestProjectConfig(), // @ts-ignore @@ -168,7 +171,7 @@ describe('javascript-sdk/react-native', () => { describe('event processor configuration', () => { // @ts-ignore - var eventProcessorSpy; + let eventProcessorSpy; beforeEach(() => { eventProcessorSpy = jest.spyOn(eventProcessor, 'createEventProcessor'); }); From b588824c3ab8e830c58bf4f8b7d952e455529a75 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:26:41 +0600 Subject: [PATCH 084/200] [FSSDK-10316] prepare for release (#937) --- CHANGELOG.md | 5 +++++ lib/index.browser.tests.js | 2 +- lib/index.lite.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- tests/index.react_native.spec.ts | 2 +- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c9c5d9b..a0fd73fdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.3.4] - Jun 28, 2024 + +### Changed + +- crypto and text encoder polyfill addition for React native ([#936](https://github.com/optimizely/javascript-sdk/pull/936)) ## [5.3.3] - Jun 06, 2024 diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 7342f3f7b..e14b91463 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -193,7 +193,7 @@ describe('javascript-sdk (Browser)', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.3'); + assert.equal(optlyInstance.clientVersion, '5.3.4'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index d9301202e..30282dcf5 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -76,7 +76,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.3'); + assert.equal(optlyInstance.clientVersion, '5.3.4'); }); }); }); diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index c12efba91..98aac4c97 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -90,7 +90,7 @@ describe('optimizelyFactory', function() { optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.3'); + assert.equal(optlyInstance.clientVersion, '5.3.4'); }); describe('event processor configuration', function() { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index c415721c5..962d06c30 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -221,7 +221,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.3.3'; +export const CLIENT_VERSION ='5.3.4' export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/package-lock.json b/package-lock.json index 223da7e68..28b366dc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.3", + "version": "5.3.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.3.3", + "version": "5.3.4", "license": "Apache-2.0", "dependencies": { "decompress-response": "^4.2.1", diff --git a/package.json b/package.json index d2ec8c05b..75d8fe42e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.3", + "version": "5.3.4", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "module": "dist/optimizely.browser.es.js", "main": "dist/optimizely.node.min.js", diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 32af4e94d..a11f40c32 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -93,7 +93,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.3.3'); + expect(optlyInstance.clientVersion).toEqual('5.3.4'); }); it('should set the React Native JS client engine and javascript SDK version', () => { From 7b611041adc8e9b4dc0a66440c493bce8db018bc Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 2 Jul 2024 23:56:14 +0600 Subject: [PATCH 085/200] [FSSDK-10372] chore(deps-dev): bump braces from 3.0.2 to 3.0.3 (#939) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 28b366dc1..048fa0849 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5545,12 +5545,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -7704,9 +7704,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -19339,12 +19339,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -20957,9 +20957,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" From 3caaff3d37aeb2fcfd1ded71089c39756ce663c7 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 3 Jul 2024 18:46:49 +0600 Subject: [PATCH 086/200] [FSSDK-10372] chore(deps): bump ws, engine.io and socket.io-adapter (#941) Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/engine.io) and [socket.io-adapter](https://github.com/socketio/socket.io-adapter). These dependencies needed to be updated together. Updates `ws` from 7.5.9 to 8.17.1 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...8.17.1) Updates `ws` from 6.2.2 to 8.17.1 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...8.17.1) Updates `ws` from 8.14.2 to 8.17.1 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...8.17.1) Updates `engine.io` from 6.5.2 to 6.5.5 - [Release notes](https://github.com/socketio/engine.io/releases) - [Changelog](https://github.com/socketio/engine.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/engine.io/compare/6.5.2...6.5.5) Updates `socket.io-adapter` from 2.5.2 to 2.5.5 - [Release notes](https://github.com/socketio/socket.io-adapter/releases) - [Changelog](https://github.com/socketio/socket.io-adapter/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io-adapter/compare/2.5.2...2.5.5) --- updated-dependencies: - dependency-name: ws dependency-type: indirect - dependency-name: ws dependency-type: indirect - dependency-name: ws dependency-type: indirect - dependency-name: engine.io dependency-type: indirect - dependency-name: socket.io-adapter dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 154 +++++++++++++++------------------------------- 1 file changed, 48 insertions(+), 106 deletions(-) diff --git a/package-lock.json b/package-lock.json index 048fa0849..5adc6bb9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3812,9 +3812,9 @@ "peer": true }, "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "peer": true, "engines": { @@ -7029,9 +7029,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz", - "integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -7043,7 +7043,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "engines": { "node": ">=10.2.0" @@ -7058,27 +7058,6 @@ "node": ">=10.0.0" } }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -11201,9 +11180,9 @@ } }, "node_modules/metro/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "peer": true, "engines": { @@ -12698,9 +12677,9 @@ } }, "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "peer": true, "engines": { @@ -12839,9 +12818,9 @@ "peer": true }, "node_modules/react-native/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "dev": true, "peer": true, "dependencies": { @@ -13663,33 +13642,13 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "dependencies": { - "ws": "~8.11.0" - } - }, - "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "debug": "~4.3.4", + "ws": "~8.17.1" } }, "node_modules/socket.io-parser": { @@ -14998,9 +14957,9 @@ } }, "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -17969,9 +17928,9 @@ "peer": true }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "peer": true, "requires": {} @@ -20453,9 +20412,9 @@ "dev": true }, "engine.io": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz", - "integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -20467,16 +20426,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, - "dependencies": { - "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "requires": {} - } + "ws": "~8.17.1" } }, "engine.io-parser": { @@ -23438,9 +23388,9 @@ "peer": true }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "peer": true, "requires": {} @@ -24834,9 +24784,9 @@ }, "dependencies": { "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "peer": true, "requires": {} @@ -24948,9 +24898,9 @@ "peer": true }, "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "dev": true, "peer": true, "requires": { @@ -25602,21 +25552,13 @@ } }, "socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "requires": { - "ws": "~8.11.0" - }, - "dependencies": { - "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "requires": {} - } + "debug": "~4.3.4", + "ws": "~8.17.1" } }, "socket.io-parser": { @@ -26598,9 +26540,9 @@ } }, "ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "requires": {} }, From f2205e8aa4698057f699270f9a1d9495481937b7 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Sat, 6 Jul 2024 01:49:23 +0600 Subject: [PATCH 087/200] fix: Remove @babel/runtime as a peerDependency (#942) Co-authored-by: Kirk Eaton <contact@kirkeaton.ca> --- package-lock.json | 5 ++++- package.json | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5adc6bb9f..bb5c04560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,6 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@babel/runtime": "^7.0.0", "@react-native-async-storage/async-storage": "^1.2.0", "@react-native-community/netinfo": "^11.3.2", "fast-text-encoding": "^1.0.6", @@ -2541,6 +2540,7 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dev": true, "peer": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -12925,6 +12925,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, "peer": true }, "node_modules/regenerator-transform": { @@ -16855,6 +16856,7 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dev": true, "peer": true, "requires": { "regenerator-runtime": "^0.14.0" @@ -24989,6 +24991,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, "peer": true }, "regenerator-transform": { diff --git a/package.json b/package.json index 75d8fe42e..71d954b7b 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,6 @@ "webpack": "^5.74.0" }, "peerDependencies": { - "@babel/runtime": "^7.0.0", "@react-native-async-storage/async-storage": "^1.2.0", "@react-native-community/netinfo": "^11.3.2", "react-native-get-random-values": "^1.11.0", From a0774efa3d4be7d607f1d603245828d2280fa1d5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 2 Aug 2024 19:34:01 +0600 Subject: [PATCH 088/200] [FSSDK-9481] update dependencies (#943) --- package-lock.json | 63 +++++++++++++++++++++++++---------------------- package.json | 10 ++++---- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb5c04560..df8d37f5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,10 @@ "version": "5.3.4", "license": "Apache-2.0", "dependencies": { - "decompress-response": "^4.2.1", + "decompress-response": "^7.0.0", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", - "ua-parser-js": "^1.0.37", + "ua-parser-js": "^1.0.38", "uuid": "^9.0.1" }, "devDependencies": { @@ -6772,14 +6772,17 @@ "dev": true }, "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", + "integrity": "sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==", "dependencies": { - "mimic-response": "^2.0.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/dedent": { @@ -10238,9 +10241,9 @@ } }, "node_modules/karma/node_modules/ua-parser-js": { - "version": "0.7.36", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.36.tgz", - "integrity": "sha512-CPPLoCts2p7D8VbybttE3P2ylv0OBZEAy7a12DsulIEcAiMtWJy+PBgMXgWDI80D5UwqE8oQPHYnk13tm38M2Q==", + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", "dev": true, "funding": [ { @@ -11257,11 +11260,11 @@ } }, "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -14516,9 +14519,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "funding": [ { "type": "opencollective", @@ -20223,11 +20226,11 @@ "dev": true }, "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", + "integrity": "sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==", "requires": { - "mimic-response": "^2.0.0" + "mimic-response": "^3.1.0" } }, "dedent": { @@ -22786,9 +22789,9 @@ } }, "ua-parser-js": { - "version": "0.7.36", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.36.tgz", - "integrity": "sha512-CPPLoCts2p7D8VbybttE3P2ylv0OBZEAy7a12DsulIEcAiMtWJy+PBgMXgWDI80D5UwqE8oQPHYnk13tm38M2Q==", + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", "dev": true }, "yargs": { @@ -23697,9 +23700,9 @@ "dev": true }, "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" }, "minimatch": { "version": "3.1.2", @@ -26222,9 +26225,9 @@ "dev": true }, "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==" + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==" }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", diff --git a/package.json b/package.json index 71d954b7b..b651ac20d 100644 --- a/package.json +++ b/package.json @@ -104,15 +104,15 @@ }, "homepage": "https://github.com/optimizely/javascript-sdk", "dependencies": { - "decompress-response": "^4.2.1", + "decompress-response": "^7.0.0", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", - "ua-parser-js": "^1.0.37", + "ua-parser-js": "^1.0.38", "uuid": "^9.0.1" }, "devDependencies": { - "@react-native-community/netinfo": "^11.3.2", "@react-native-async-storage/async-storage": "^1.2.0", + "@react-native-community/netinfo": "^11.3.2", "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/chai": "^4.2.11", @@ -162,8 +162,8 @@ "peerDependencies": { "@react-native-async-storage/async-storage": "^1.2.0", "@react-native-community/netinfo": "^11.3.2", - "react-native-get-random-values": "^1.11.0", - "fast-text-encoding": "^1.0.6" + "fast-text-encoding": "^1.0.6", + "react-native-get-random-values": "^1.11.0" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { From 093c3ca080bdb20c7c21b8d610c96dbace1c5138 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 22 Aug 2024 20:03:01 +0600 Subject: [PATCH 089/200] [FSSDK-10579] replace Jest with Vitest (#944) --- .devcontainer/devcontainer.json | 4 +- .github/workflows/javascript.yml | 2 +- .vscode/settings.json | 5 +- jest.config.js | 27 - .../httpPollingDatafileManager.ts | 1 - package-lock.json | 16610 ++++------------ package.json | 11 +- tests/backoffController.spec.ts | 4 +- tests/browserAsyncStorageCache.spec.ts | 10 +- tests/browserDatafileManager.spec.ts | 20 +- tests/browserRequest.spec.ts | 6 +- tests/browserRequestHandler.spec.ts | 6 +- tests/buildEventV1.spec.ts | 4 +- tests/eventEmitter.spec.ts | 30 +- tests/eventQueue.spec.ts | 50 +- tests/httpPollingDatafileManager.spec.ts | 72 +- .../httpPollingDatafileManagerPolling.spec.ts | 3 +- tests/index.react_native.spec.ts | 48 +- tests/jsconfig.json | 7 - tests/logger.spec.ts | 31 +- tests/nodeDatafileManager.spec.ts | 27 +- tests/nodeRequest.spec.ts | 9 +- tests/nodeRequestHandler.spec.ts | 18 +- tests/odpEventApiManager.spec.ts | 2 +- tests/odpEventManager.spec.ts | 58 +- tests/odpManager.browser.spec.ts | 1 + tests/odpManager.spec.ts | 11 +- tests/odpSegmentApiManager.spec.ts | 4 +- tests/odpSegmentManager.spec.ts | 2 +- tests/pendingEventsDispatcher.spec.ts | 63 +- tests/pendingEventsStore.spec.ts | 4 +- tests/reactNativeAsyncStorageCache.spec.ts | 4 +- ....ts => reactNativeDatafileManager.spec.ts} | 86 +- tests/reactNativeEventsStore.spec.ts | 70 +- ...ctNativeHttpPollingDatafileManager.spec.ts | 17 +- tests/reactNativeV1EventProcessor.spec.ts | 11 +- tests/requestTracker.spec.ts | 1 + tests/sendBeaconDispatcher.spec.ts | 93 +- tests/testUtils.ts | 21 +- tests/utils.spec.ts | 3 +- tests/v1EventProcessor.react_native.spec.ts | 38 +- tests/v1EventProcessor.spec.ts | 192 +- tests/vuidManager.spec.ts | 2 +- tsconfig.spec.json | 3 +- vitest.config.mts | 12 + 45 files changed, 4032 insertions(+), 13671 deletions(-) delete mode 100644 jest.config.js delete mode 100644 tests/jsconfig.json rename tests/{reactNativeDatafileManger.spec.ts => reactNativeDatafileManager.spec.ts} (69%) create mode 100644 vitest.config.mts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bda7bb833..7b737155f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,8 +12,8 @@ "esbenp.prettier-vscode", "Gruntfuggly.todo-tree", "github.vscode-github-actions", - "Orta.vscode-jest", - "ms-vscode.test-adapter-converter" + "ms-vscode.test-adapter-converter", + "vitest.explorer" ] } } diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index cf3f8c49f..137b1dbe2 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ['14', '16', '18', '20'] + node: ['16', '18', '20', '22'] steps: - uses: actions/checkout@v3 - name: Set up Node ${{ matrix.node }} diff --git a/.vscode/settings.json b/.vscode/settings.json index 7869db3b0..ce072c82c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,3 @@ { - "jest.rootPath": "/workspaces/javascript-sdk/packages/optimizely-sdk", - "jest.jestCommandLine": "./node_modules/.bin/jest", - "jest.autoRevealOutput": "on-exec-error", "editor.tabSize": 2 -} \ No newline at end of file +} diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index d8ce6a217..000000000 --- a/jest.config.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - "transform": { - "^.+\\.(ts|tsx|js|jsx)$": "ts-jest", - }, - "testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.tsx?$", - moduleNameMapper: { - // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451 - "uuid": require.resolve('uuid'), - }, - "testPathIgnorePatterns" : [ - "tests/testUtils.ts", - "dist" - ], - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], - "resetMocks": false, - "setupFiles": [ - "jest-localstorage-mock", - ], - testEnvironment: "jsdom" -} diff --git a/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/lib/modules/datafile-manager/httpPollingDatafileManager.ts index c3311997e..6dfce4c37 100644 --- a/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ b/lib/modules/datafile-manager/httpPollingDatafileManager.ts @@ -115,7 +115,6 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana urlTemplate = DEFAULT_URL_TEMPLATE, cache = noOpKeyValueCache, } = configWithDefaultsApplied; - this.cache = cache; this.cacheKey = 'opt-datafile-' + sdkKey; this.sdkKey = sdkKey; diff --git a/package-lock.json b/package-lock.json index df8d37f5b..2725c62e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "@optimizely/optimizely-sdk", "version": "5.3.4", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -21,7 +21,6 @@ "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/chai": "^4.2.11", - "@types/jest": "^29.5.12", "@types/mocha": "^5.2.7", "@types/nise": "^1.4.0", "@types/node": "^18.7.18", @@ -29,14 +28,13 @@ "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", + "@vitest/coverage-istanbul": "^2.0.5", "chai": "^4.2.0", "coveralls-next": "^4.2.0", "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.0.0", - "jest-localstorage-mock": "^2.4.22", + "happy-dom": "^14.12.3", "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", @@ -62,6 +60,7 @@ "ts-node": "^8.10.2", "tslib": "^2.4.0", "typescript": "^4.7.4", + "vitest": "^2.0.5", "webpack": "^5.74.0" }, "engines": { @@ -111,115 +110,44 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", - "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.20", - "@babel/helpers": "^7.22.15", - "@babel/parser": "^7.22.16", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.20", - "@babel/types": "^7.22.19", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", @@ -233,6 +161,12 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -243,14 +177,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -258,40 +192,41 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dev": true, "peer": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "dev": true, "peer": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -324,20 +259,18 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", - "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", "semver": "^6.3.1" }, "engines": { @@ -358,13 +291,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" }, @@ -386,9 +319,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "dev": true, "peer": true, "dependencies": { @@ -402,76 +335,43 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, "peer": true, "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -481,37 +381,38 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dev": true, "peer": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, + "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -521,15 +422,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -539,107 +440,97 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dev": true, "peer": true, "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -717,10 +608,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -728,14 +622,15 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" }, "engines": { "node": ">=6.9.0" @@ -744,33 +639,30 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", - "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -779,24 +671,39 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-class-properties": { @@ -818,14 +725,14 @@ } }, "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.23.3.tgz", - "integrity": "sha512-Q23MpLZfSGZL1kU7fWqV262q65svLSCIP5kZ/JCW/rKTCm/FrLjpvEd2kfUYMVeHh4QhV/xzyoRAHWrAZJrE3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.7.tgz", + "integrity": "sha512-CcmFwUJ3tKhLjPdt4NP+SHMshebytF8ZTYOv5ZDpkzq2sin80Wb5vJrGt8fhPrORQCfoSa0LAxC/DW+GAC5+Hw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-default-from": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-default-from": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -852,63 +759,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", - "dev": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", @@ -946,6 +796,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -958,6 +809,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -970,6 +822,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -1007,13 +860,13 @@ } }, "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.23.3.tgz", - "integrity": "sha512-KeENO5ck1IeZ/l2lFZNy+mpobV3D2Zy5C1YFnWm+YuY5mQiAWc4yAp13dqgguwsBsFVLh4LPCEqCa5qW13N+hw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.7.tgz", + "integrity": "sha512-bTPz4/635WQ9WhwsyPdxUJDVpsi/X9BMmy/8Rf/UAlOO4jSql4CxUCjWI5PiM+jG+c4LVPTScoTw80geFj9+Bw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1036,13 +889,13 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", - "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", + "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1052,13 +905,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1068,13 +921,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1088,6 +941,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1100,6 +954,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1108,12 +963,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1127,6 +983,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1139,6 +996,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1151,6 +1009,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1163,6 +1022,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1175,6 +1035,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1187,6 +1048,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1215,6 +1077,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1226,12 +1089,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1258,13 +1122,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1274,16 +1138,16 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", - "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1293,15 +1157,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1311,13 +1175,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1327,13 +1191,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1343,14 +1207,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1360,14 +1224,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -1378,19 +1242,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", - "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz", + "integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.0", "globals": "^11.1.0" }, "engines": { @@ -1411,14 +1273,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1428,13 +1290,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1444,14 +1306,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1461,13 +1323,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1476,14 +1338,31 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1494,14 +1373,14 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1511,13 +1390,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1528,14 +1407,14 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", - "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.2.tgz", + "integrity": "sha512-InBZ0O8tew5V0K6cHcQ+wgxlrjOw1W4wDXLkOTjLRD8GYhTSkxTVBtdy3MMtvYBrbAWa1Qm3hNoTc1620Yj+Mg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-flow": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-flow": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1545,14 +1424,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1562,15 +1441,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" }, "engines": { "node": ">=6.9.0" @@ -1580,13 +1459,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1597,13 +1476,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1613,13 +1492,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1630,13 +1509,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1646,14 +1525,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1663,15 +1542,15 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1681,16 +1560,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", - "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1700,14 +1579,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1717,14 +1596,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1734,13 +1613,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1750,13 +1629,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -1767,13 +1646,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -1784,17 +1663,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "dev": true, "peer": true, "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1804,14 +1682,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1821,13 +1699,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -1838,14 +1716,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -1856,13 +1734,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1872,14 +1750,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1889,15 +1767,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1908,13 +1786,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1924,13 +1802,13 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", - "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1940,17 +1818,17 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -1960,13 +1838,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1976,13 +1854,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1992,13 +1870,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2009,13 +1887,13 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2025,17 +1903,17 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz", - "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -2056,13 +1934,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2072,14 +1950,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2089,13 +1967,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2105,13 +1983,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2121,13 +1999,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2137,16 +2015,17 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", - "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.23.3" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2156,13 +2035,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2172,14 +2051,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2189,14 +2068,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2206,14 +2085,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2223,27 +2102,29 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", - "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", "dev": true, "peer": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -2255,59 +2136,60 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.9", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.9", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -2328,15 +2210,15 @@ } }, "node_modules/@babel/preset-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.23.3.tgz", - "integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.7.tgz", + "integrity": "sha512-NL3Lo0NorCU607zU3NwRyJbpaB6E3t0xtd3LfAQKDfkeX4/ggcDXvkmkW42QWT5owUeW/jAe4hn+2qvkV1IbfQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-flow-strip-types": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-transform-flow-strip-types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2361,17 +2243,17 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", - "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-typescript": "^7.23.3" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2381,9 +2263,9 @@ } }, "node_modules/@babel/register": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", - "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", "dev": true, "peer": true, "dependencies": { @@ -2537,9 +2419,9 @@ "peer": true }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dev": true, "peer": true, "dependencies": { @@ -2549,34 +2431,38 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "peer": true + }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2594,13 +2480,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2611,7 +2497,8 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -2648,25 +2535,393 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=12" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2743,6 +2998,7 @@ "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -2770,28 +3026,125 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "peer": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" }, "engines": { @@ -2873,6 +3226,7 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -2890,6 +3244,7 @@ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, + "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -2937,6 +3292,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -2963,6 +3319,7 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, + "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -2984,6 +3341,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, + "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -2999,6 +3357,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, + "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -3014,7 +3373,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@jest/core/node_modules/diff": { "version": "4.0.2", @@ -3032,6 +3392,7 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -3077,6 +3438,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -3102,6 +3464,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -3169,6 +3532,7 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, + "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -3184,6 +3548,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, + "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -3197,6 +3562,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, + "peer": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -3209,6 +3575,7 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -3226,6 +3593,7 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -3241,6 +3609,7 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -3284,6 +3653,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -3309,13 +3679,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -3332,6 +3704,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -3357,6 +3730,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -3378,6 +3752,7 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -3392,6 +3767,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, + "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -3407,6 +3783,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, + "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -3422,6 +3799,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -3447,6 +3825,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -3469,14 +3848,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3492,9 +3871,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -3517,9 +3896,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3561,6 +3940,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@react-native-async-storage/async-storage": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz", @@ -3574,69 +3963,68 @@ } }, "node_modules/@react-native-community/cli": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", - "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-14.0.0.tgz", + "integrity": "sha512-KwMKJB5jsDxqOhT8CGJ55BADDAYxlYDHv5R/ASQlEcdBEZxT0zZmnL0iiq2VqzETUy+Y/Nop+XDFgqyoQm0C2w==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-clean": "12.3.2", - "@react-native-community/cli-config": "12.3.2", - "@react-native-community/cli-debugger-ui": "12.3.2", - "@react-native-community/cli-doctor": "12.3.2", - "@react-native-community/cli-hermes": "12.3.2", - "@react-native-community/cli-plugin-metro": "12.3.2", - "@react-native-community/cli-server-api": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "@react-native-community/cli-types": "12.3.2", + "@react-native-community/cli-clean": "14.0.0", + "@react-native-community/cli-config": "14.0.0", + "@react-native-community/cli-debugger-ui": "14.0.0", + "@react-native-community/cli-doctor": "14.0.0", + "@react-native-community/cli-server-api": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", + "@react-native-community/cli-types": "14.0.0", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", "execa": "^5.0.0", - "find-up": "^4.1.0", + "find-up": "^5.0.0", "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "prompts": "^2.4.2", "semver": "^7.5.2" }, "bin": { - "react-native": "build/bin.js" + "rnc-cli": "build/bin.js" }, "engines": { "node": ">=18" } }, "node_modules/@react-native-community/cli-clean": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", - "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-14.0.0.tgz", + "integrity": "sha512-kvHthZTNur/wLLx8WL5Oh+r04zzzFAX16r8xuaLhu9qGTE6Th1JevbsIuiQb5IJqD8G/uZDKgIZ2a0/lONcbJg==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-tools": "14.0.0", "chalk": "^4.1.2", - "execa": "^5.0.0" + "execa": "^5.0.0", + "fast-glob": "^3.3.2" } }, "node_modules/@react-native-community/cli-config": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", - "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-14.0.0.tgz", + "integrity": "sha512-2Nr8KR+dgn1z+HLxT8piguQ1SoEzgKJnOPQKE1uakxWaRFcQ4LOXgzpIAscYwDW6jmQxdNqqbg2cRUoOS7IMtQ==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-tools": "14.0.0", "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", + "cosmiconfig": "^9.0.0", "deepmerge": "^4.3.0", - "glob": "^7.1.3", + "fast-glob": "^3.3.2", "joi": "^17.2.1" } }, "node_modules/@react-native-community/cli-debugger-ui": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", - "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0.tgz", + "integrity": "sha512-JpfzILfU7eKE9+7AMCAwNJv70H4tJGVv3ZGFqSVoK1YHg5QkVEGsHtoNW8AsqZRS6Fj4os+Fmh+r+z1L36sPmg==", "dev": true, "peer": true, "dependencies": { @@ -3644,23 +4032,22 @@ } }, "node_modules/@react-native-community/cli-doctor": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", - "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-14.0.0.tgz", + "integrity": "sha512-in6jylHjaPUaDzV+JtUblh8m9JYIHGjHOf6Xn57hrmE5Zwzwuueoe9rSMHF1P0mtDgRKrWPzAJVejElddfptWA==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-config": "12.3.2", - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-platform-ios": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-config": "14.0.0", + "@react-native-community/cli-platform-android": "14.0.0", + "@react-native-community/cli-platform-apple": "14.0.0", + "@react-native-community/cli-platform-ios": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", "chalk": "^4.1.2", "command-exists": "^1.2.8", "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", + "envinfo": "^7.13.0", "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", "node-stream-zip": "^1.9.1", "ora": "^5.4.1", "semver": "^7.5.2", @@ -3692,73 +4079,62 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli-hermes": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", - "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", - "dev": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - } - }, "node_modules/@react-native-community/cli-platform-android": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", - "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-14.0.0.tgz", + "integrity": "sha512-nt7yVz3pGKQXnVa5MAk7zR+1n41kNKD3Hi2OgybH5tVShMBo7JQoL2ZVVH6/y/9wAwI/s7hXJgzf1OIP3sMq+Q==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-tools": "14.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", + "fast-glob": "^3.3.2", "fast-xml-parser": "^4.2.4", - "glob": "^7.1.3", "logkitty": "^0.7.1" } }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", - "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", + "node_modules/@react-native-community/cli-platform-apple": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-14.0.0.tgz", + "integrity": "sha512-WniJL8vR4MeIsjqio2hiWWuUYUJEL3/9TDL5aXNwG68hH3tYgK3742+X9C+vRzdjTmf5IKc/a6PwLsdplFeiwQ==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-tools": "14.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", - "fast-xml-parser": "^4.0.12", - "glob": "^7.1.3", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.2.4", "ora": "^5.4.1" } }, - "node_modules/@react-native-community/cli-plugin-metro": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", - "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==", + "node_modules/@react-native-community/cli-platform-ios": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-14.0.0.tgz", + "integrity": "sha512-8kxGv7mZ5nGMtueQDq+ndu08f0ikf3Zsqm3Ix8FY5KCXpSgP14uZloO2GlOImq/zFESij+oMhCkZJGggpWpfAw==", "dev": true, - "peer": true + "peer": true, + "dependencies": { + "@react-native-community/cli-platform-apple": "14.0.0" + } }, "node_modules/@react-native-community/cli-server-api": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", - "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0.tgz", + "integrity": "sha512-A0FIsj0QCcDl1rswaVlChICoNbfN+mkrKB5e1ab5tOYeZMMyCHqvU+eFvAvXjHUlIvVI+LbqCkf4IEdQ6H/2AQ==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-debugger-ui": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-debugger-ui": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", "nocache": "^3.0.1", "pretty-format": "^26.6.2", "serve-static": "^1.13.1", - "ws": "^7.5.1" + "ws": "^6.2.3" } }, "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { @@ -3812,39 +4188,27 @@ "peer": true }, "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "dev": true, "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "dependencies": { + "async-limiter": "~1.0.0" } }, "node_modules/@react-native-community/cli-tools": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", - "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0.tgz", + "integrity": "sha512-L7GX5hyYYv0ZWbAyIQKzhHuShnwDqlKYB0tqn57wa5riGCaxYuRPTK+u4qy+WRCye7+i8M4Xj6oQtSd4z0T9cA==", "dev": true, "peer": true, "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", + "execa": "^5.0.0", "find-up": "^5.0.0", "mime": "^2.4.1", - "node-fetch": "^2.6.0", "open": "^6.2.0", "ora": "^5.4.1", "semver": "^7.5.2", @@ -3853,95 +4217,28 @@ } }, "node_modules/@react-native-community/cli-types": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", - "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-14.0.0.tgz", + "integrity": "sha512-CMUevd1pOWqvmvutkUiyQT2lNmMHUzSW7NKc1xvHgg39NjbS58Eh2pMzIUP85IwbYNeocfYc3PH19vA/8LnQtg==", "dev": true, "peer": true, "dependencies": { "joi": "^17.2.1" } }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "peer": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@react-native-community/cli/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@react-native-community/netinfo": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz", "integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==", "dev": true, - "license": "MIT", "peerDependencies": { "react-native": ">=0.59" } }, "node_modules/@react-native/assets-registry": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", - "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.2.tgz", + "integrity": "sha512-P1dLHjpUeC0AIkDHRYcx0qLMr+p92IPWL3pmczzo6T76Qa9XzruQOYy0jittxyBK91Csn6HHQ/eit8TeXW8MVw==", "dev": true, "peer": true, "engines": { @@ -3949,50 +4246,52 @@ } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", - "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.2.tgz", + "integrity": "sha512-BIKVh2ZJPkzluUGgCNgpoh6NTHgX8j04FCS0Z/rTmRJ66hir/EUBl8frMFKrOy/6i4VvZEltOWB5eWfHe1AYgw==", "dev": true, "peer": true, "dependencies": { - "@react-native/codegen": "0.73.3" + "@react-native/codegen": "0.75.2" }, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-preset": { - "version": "0.73.21", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", - "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.2.tgz", + "integrity": "sha512-mprpsas+WdCEMjQZnbDiAC4KKRmmLbMB+o/v4mDqKlH4Mcm7RdtP5t80MZGOVCHlceNp1uEIpXywx69DNwgbgg==", "dev": true, "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", "@babel/plugin-syntax-dynamic-import": "^7.8.0", "@babel/plugin-syntax-export-default-from": "^7.0.0", "@babel/plugin-syntax-flow": "^7.18.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", "@babel/plugin-syntax-optional-chaining": "^7.0.0", "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", "@babel/plugin-transform-async-to-generator": "^7.20.0", "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-class-properties": "^7.24.1", "@babel/plugin-transform-classes": "^7.0.0", "@babel/plugin-transform-computed-properties": "^7.0.0", "@babel/plugin-transform-destructuring": "^7.20.0", "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-for-of": "^7.0.0", "@babel/plugin-transform-function-name": "^7.0.0", "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", "@babel/plugin-transform-modules-commonjs": "^7.0.0", "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.5", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.5", "@babel/plugin-transform-parameters": "^7.0.0", "@babel/plugin-transform-private-methods": "^7.22.5", "@babel/plugin-transform-private-property-in-object": "^7.22.11", @@ -4000,6 +4299,7 @@ "@babel/plugin-transform-react-jsx": "^7.0.0", "@babel/plugin-transform-react-jsx-self": "^7.0.0", "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-regenerator": "^7.20.0", "@babel/plugin-transform-runtime": "^7.0.0", "@babel/plugin-transform-shorthand-properties": "^7.0.0", "@babel/plugin-transform-spread": "^7.0.0", @@ -4007,7 +4307,7 @@ "@babel/plugin-transform-typescript": "^7.5.0", "@babel/plugin-transform-unicode-regex": "^7.0.0", "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.4", + "@react-native/babel-plugin-codegen": "0.75.2", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, @@ -4019,19 +4319,20 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", - "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.2.tgz", + "integrity": "sha512-OkWdbtO2jTkfOXfj3ibIL27rM6LoaEuApOByU2G8X+HS6v9U87uJVJlMIRWBDmnxODzazuHwNVA2/wAmSbucaw==", "dev": true, "peer": true, "dependencies": { "@babel/parser": "^7.20.0", - "flow-parser": "^0.206.0", "glob": "^7.1.1", + "hermes-parser": "0.22.0", "invariant": "^2.2.4", "jscodeshift": "^0.14.0", "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" }, "engines": { "node": ">=18" @@ -4041,76 +4342,186 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.73.16", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.16.tgz", - "integrity": "sha512-eNH3v3qJJF6f0n/Dck90qfC9gVOR4coAXMTdYECO33GfgjTi+73vf/SBqlXw9HICH/RNZYGPM3wca4FRF7TYeQ==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.2.tgz", + "integrity": "sha512-/tz0bzVja4FU0aAimzzQ7iYR43peaD6pzksArdrrGhlm8OvFYAQPOYSNeIQVMSarwnkNeg1naFKaeYf1o3++yA==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-server-api": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "@react-native/dev-middleware": "0.73.7", - "@react-native/metro-babel-transformer": "0.73.15", + "@react-native-community/cli-server-api": "14.0.0-alpha.11", + "@react-native-community/cli-tools": "14.0.0-alpha.11", + "@react-native/dev-middleware": "0.75.2", + "@react-native/metro-babel-transformer": "0.75.2", "chalk": "^4.0.0", "execa": "^5.1.1", "metro": "^0.80.3", "metro-config": "^0.80.3", "metro-core": "^0.80.3", "node-fetch": "^2.2.0", + "querystring": "^0.2.1", "readline": "^1.3.0" }, "engines": { "node": ">=18" } }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", - "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "node_modules/@react-native/community-cli-plugin/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": ">= 10.14.2" } }, - "node_modules/@react-native/dev-middleware": { - "version": "0.73.7", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz", - "integrity": "sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg==", + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-debugger-ui": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0-alpha.11.tgz", + "integrity": "sha512-0wCNQxhCniyjyMXgR1qXliY180y/2QbvoiYpp2MleGQADr5M1b8lgI4GoyADh5kE+kX3VL0ssjgyxpmbpCD86A==", "dev": true, "peer": true, "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.73.3", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^1.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "open": "^7.0.3", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0" - }, - "engines": { - "node": ">=18" + "serve-static": "^1.13.1" } }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-server-api": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0-alpha.11.tgz", + "integrity": "sha512-I7YeYI7S5wSxnQAqeG8LNqhT99FojiGIk87DU0vTp6U8hIMLcA90fUuBAyJY38AuQZ12ZJpGa8ObkhIhWzGkvg==", "dev": true, "peer": true, "dependencies": { - "ms": "2.0.0" + "@react-native-community/cli-debugger-ui": "14.0.0-alpha.11", + "@react-native-community/cli-tools": "14.0.0-alpha.11", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^6.2.3" } }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-tools": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0-alpha.11.tgz", + "integrity": "sha512-HQCfVnX9aqRdKdLxmQy4fUAUo+YhNGlBV7ZjOayPbuEGWJ4RN+vSy0Cawk7epo7hXd6vKzc7P7y3HlU6Kxs7+w==", "dev": true, - "peer": true + "peer": true, + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/community-cli-plugin/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.2.tgz", + "integrity": "sha512-qIC6mrlG8RQOPaYLZQiJwqnPchAVGnHWcVDeQxPMPLkM/D5+PC8tuKWYOwgLcEau3RZlgz7QQNk31Qj2/OJG6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.2.tgz", + "integrity": "sha512-fTC5m2uVjYp1XPaIJBFgscnQjPdGVsl96z/RfLgXDq0HBffyqbg29ttx6yTCx7lIa9Gdvf6nKQom+e+Oa4izSw==", + "dev": true, + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.75.2", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.13.1", + "ws": "^6.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true }, "node_modules/@react-native/dev-middleware/node_modules/open": { "version": "7.4.2", @@ -4129,10 +4540,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/@react-native/gradle-plugin": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", - "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.2.tgz", + "integrity": "sha512-AELeAOCZi3B2vE6SeN+mjpZjjqzqa76yfFBB3L3f3NWiu4dm/YClTGOj+5IVRRgbt8LDuRImhDoaj7ukheXr4Q==", "dev": true, "peer": true, "engines": { @@ -4140,9 +4561,9 @@ } }, "node_modules/@react-native/js-polyfills": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", - "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.2.tgz", + "integrity": "sha512-AtLd3mbiE+FXK2Ru3l2NFOXDhUvzdUsCP4qspUw0haVaO/9xzV97RVD2zz0lur2f/LmZqQ2+KXyYzr7048b5iw==", "dev": true, "peer": true, "engines": { @@ -4150,15 +4571,15 @@ } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.15", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", - "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.2.tgz", + "integrity": "sha512-EygglCCuOub2sZ00CSIiEekCXoGL2XbOC6ssOB47M55QKvhdPG/0WBQXvmOmiN42uZgJK99Lj749v4rB0PlPIQ==", "dev": true, "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.21", - "hermes-parser": "0.15.0", + "@react-native/babel-preset": "0.75.2", + "hermes-parser": "0.22.0", "nullthrows": "^1.1.1" }, "engines": { @@ -4169,16 +4590,16 @@ } }, "node_modules/@react-native/normalize-colors": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", - "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.2.tgz", + "integrity": "sha512-nPwWJFtsqNFS/qSG9yDOiSJ64mjG7RCP4X/HXFfyWzCM1jq49h/DYBdr+c3e7AvTKGIdy0gGT3vgaRUHZFVdUQ==", "dev": true, "peer": true }, "node_modules/@react-native/virtualized-lists": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", - "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.2.tgz", + "integrity": "sha512-pD5SVCjxc8k+JdoyQ+IlulBTEqJc3S4KUKsmv5zqbNCyETB0ZUvd4Su7bp+lLF6ALxx6KKmbGk8E3LaWEjUFFQ==", "dev": true, "peer": true, "dependencies": { @@ -4189,7 +4610,14 @@ "node": ">=18" }, "peerDependencies": { + "@types/react": "^18.2.6", + "react": "*", "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@rollup/plugin-commonjs": { @@ -4249,6 +4677,214 @@ "rollup": "^1.20.0||^2.0.0" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", + "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", + "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", + "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", + "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", + "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", + "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", + "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", + "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", + "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", + "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", + "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", + "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", + "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", + "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", + "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", + "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4293,6 +4929,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, + "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -4302,6 +4939,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, + "peer": true, "dependencies": { "type-detect": "4.0.8" } @@ -4344,14 +4982,16 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">= 10" } }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true, "optional": true, "peer": true @@ -4385,6 +5025,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", "dev": true, + "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -4398,6 +5039,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", "dev": true, + "peer": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -4407,6 +5049,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", "dev": true, + "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -4417,6 +5060,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", "dev": true, + "peer": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -4473,6 +5117,7 @@ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", "dev": true, + "peer": true, "dependencies": { "@types/node": "*" } @@ -4501,27 +5146,6 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/jsdom": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", - "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, "node_modules/@types/json-schema": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", @@ -4546,6 +5170,16 @@ "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", "dev": true }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -4565,13 +5199,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", - "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/ua-parser-js": { "version": "0.7.37", @@ -4788,26 +5417,364 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "node_modules/@vitest/coverage-istanbul": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.0.5.tgz", + "integrity": "sha512-BvjWKtp7fiMAeYUD0mO5cuADzn1gmjTm54jm5qUEnh/O08riczun8rI4EtQlg3bWoRo2lT3FO8DmjPDX9ZthPw==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@istanbuljs/schema": "^0.1.3", + "debug": "^4.3.5", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-instrument": "^6.0.3", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magicast": "^0.3.4", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "2.0.5" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "node_modules/@vitest/coverage-istanbul/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/expect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vitest/expect/node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vitest/expect/node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/@vitest/expect/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@vitest/expect/node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/@vitest/expect/node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", + "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", + "dev": true, + "dependencies": { + "@vitest/utils": "2.0.5", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@vitest/spy": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@vitest/utils/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { @@ -4951,7 +5918,9 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/abort-controller": { "version": "3.0.0", @@ -4996,6 +5965,8 @@ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" @@ -5024,6 +5995,8 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.4.0" } @@ -5099,6 +6072,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -5114,6 +6088,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -5315,6 +6290,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -5327,14 +6303,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", - "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", "dev": true, "peer": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.5.0", + "@babel/helper-define-polyfill-provider": "^0.6.2", "semver": "^6.3.1" }, "peerDependencies": { @@ -5352,27 +6328,27 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", - "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0", - "core-js-compat": "^3.34.0" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", - "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "dev": true, "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5393,6 +6369,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, + "peer": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -5563,9 +6540,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -5582,10 +6559,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -5667,6 +6644,7 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, + "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -5723,6 +6701,15 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -5842,9 +6829,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001585", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", - "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -5900,6 +6887,7 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "peer": true, "engines": { "node": ">=10" } @@ -5969,9 +6957,9 @@ } }, "node_modules/chromium-edge-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", - "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", "dev": true, "peer": true, "dependencies": { @@ -6015,7 +7003,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/clean-stack": { "version": "2.2.0", @@ -6057,6 +7046,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -6096,6 +7086,7 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, + "peer": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -6105,7 +7096,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/color-convert": { "version": "2.0.1", @@ -6151,6 +7143,16 @@ "dev": true, "peer": true }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -6284,13 +7286,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", - "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", "dev": true, "peer": true, "dependencies": { - "browserslist": "^4.22.2" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -6298,9 +7300,9 @@ } }, "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, "peer": true }, @@ -6318,57 +7320,50 @@ } }, "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "peer": true, "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/cosmiconfig/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "peer": true, - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + "peer": true }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "peer": true, "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/coveralls-next": { @@ -6422,6 +7417,7 @@ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -6443,6 +7439,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -6469,6 +7466,7 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, + "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -6490,6 +7488,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, + "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -6505,6 +7504,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, + "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -6520,7 +7520,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/create-jest/node_modules/diff": { "version": "4.0.2", @@ -6538,6 +7539,7 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -6583,6 +7585,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -6608,6 +7611,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -6683,13 +7687,17 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/cssstyle": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "cssom": "~0.3.6" }, @@ -6701,7 +7709,9 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/custom-event": { "version": "1.0.1", @@ -6714,6 +7724,8 @@ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", @@ -6733,9 +7745,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", "dev": true, "peer": true }, @@ -6769,7 +7781,9 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/decompress-response": { "version": "7.0.0", @@ -6790,6 +7804,7 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", "dev": true, + "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -6822,6 +7837,7 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6879,21 +7895,6 @@ "node": ">= 0.8" } }, - "node_modules/deprecated-react-native-prop-types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", - "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", - "dev": true, - "peer": true, - "dependencies": { - "@react-native/normalize-colors": "^0.73.0", - "invariant": "^2.2.4", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -6909,6 +7910,7 @@ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -6933,6 +7935,7 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -6979,6 +7982,8 @@ "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "deprecated": "Use your platform's native DOMException instead", "dev": true, + "optional": true, + "peer": true, "dependencies": { "webidl-conversions": "^7.0.0" }, @@ -6992,6 +7997,12 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6999,9 +8010,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.661", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.661.tgz", - "integrity": "sha512-AFg4wDHSOk5F+zA8aR+SVIOabu7m0e7BiJnigCvPXzIGy731XENw/lmNxTySpVFtkFEy+eyt4oHhh5FF3NjQNw==", + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz", + "integrity": "sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA==", "dev": true }, "node_modules/emittery": { @@ -7009,6 +8020,7 @@ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -7092,10 +8104,20 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", - "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", "dev": true, "peer": true, "bin": { @@ -7110,6 +8132,7 @@ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, + "peer": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -7165,10 +8188,48 @@ "es6-promise": "^4.0.3" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -7197,6 +8258,8 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -7218,6 +8281,8 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -7543,6 +8608,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -7566,6 +8632,7 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -7575,6 +8642,7 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, + "peer": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -7586,6 +8654,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true, + "peer": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7605,9 +8680,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7633,9 +8708,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", - "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "funding": [ { @@ -7669,6 +8744,7 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "peer": true, "dependencies": { "bser": "2.1.1" } @@ -7845,9 +8921,9 @@ "peer": true }, "node_modules/flow-parser": { - "version": "0.206.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", - "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "version": "0.244.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.244.0.tgz", + "integrity": "sha512-Dkc88m5k8bx1VvHTO9HEJ7tvMcSb3Zvcv1PY4OHK7pHdtdY2aUjhmPy6vpjVJ2uUUOIybRlb91sXE8g4doChtA==", "dev": true, "peer": true, "engines": { @@ -8073,6 +9149,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -8084,6 +9161,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -8165,6 +9243,20 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/happy-dom": { + "version": "14.12.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.12.3.tgz", + "integrity": "sha512-vsYlEs3E9gLwA1Hp+w3qzu+RUDFf4VTT8cyKqVICoZ2k7WM++Qyd2LwzyTi5bqMJFiIC/vNpTDYuxdreENRK/g==", + "dev": true, + "dependencies": { + "entities": "^4.5.0", + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8245,43 +9337,20 @@ } }, "node_modules/hermes-estree": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", - "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.22.0.tgz", + "integrity": "sha512-FLBt5X9OfA8BERUdc6aZS36Xz3rRuB0Y/mfocSADWEJfomc1xfene33GdyAmtTkKTBXTN/EgAy+rjTKkkZJHlw==", "dev": true, "peer": true }, "node_modules/hermes-parser": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", - "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", - "dev": true, - "peer": true, - "dependencies": { - "hermes-estree": "0.15.0" - } - }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.22.0.tgz", + "integrity": "sha512-gn5RfZiEXCsIWsFGsKiykekktUoh0PdFWYocXsUdZIyWSckT6UIyPcyyUIPSR3kpnELWeK3n3ztAse7Mat6PSA==", "dev": true, "peer": true, "dependencies": { - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hermes-profile-transformer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" + "hermes-estree": "0.22.0" } }, "node_modules/html-encoding-sniffer": { @@ -8289,6 +9358,8 @@ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "whatwg-encoding": "^2.0.0" }, @@ -8337,6 +9408,8 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -8364,6 +9437,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "peer": true, "engines": { "node": ">=10.17.0" } @@ -8373,6 +9447,8 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8447,6 +9523,7 @@ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, + "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -8483,6 +9560,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -8505,18 +9583,12 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "dev": true, - "peer": true - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -8591,6 +9663,7 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -8667,7 +9740,9 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/is-reference": { "version": "1.2.1", @@ -8771,9 +9846,9 @@ } }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "engines": { "node": ">=8" @@ -8796,6 +9871,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -8812,6 +9888,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -8871,9 +9948,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -8883,11 +9960,27 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -8914,6 +10007,7 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, + "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -8928,6 +10022,7 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -8959,6 +10054,7 @@ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -8992,6 +10088,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -9018,6 +10115,7 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, + "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -9039,6 +10137,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, + "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -9054,6 +10153,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, + "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -9069,7 +10169,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/jest-cli/node_modules/diff": { "version": "4.0.2", @@ -9087,6 +10188,7 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -9132,6 +10234,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -9157,6 +10260,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9211,6 +10315,7 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, + "peer": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -9226,6 +10331,7 @@ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, + "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -9238,6 +10344,7 @@ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -9249,38 +10356,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-jsdom": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", - "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0", - "jsdom": "^20.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -9298,6 +10379,7 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9307,6 +10389,7 @@ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, + "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -9315,20 +10398,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-localstorage-mock": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz", - "integrity": "sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==", - "dev": true, - "engines": { - "node": ">=6.16.0" - } - }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, + "peer": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -9344,6 +10419,7 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -9364,6 +10440,7 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -9378,6 +10455,7 @@ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, + "peer": true, "engines": { "node": ">=6" }, @@ -9395,6 +10473,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, + "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -9415,6 +10494,7 @@ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, + "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -9428,6 +10508,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9437,6 +10518,7 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -9462,6 +10544,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9471,6 +10554,7 @@ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, + "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -9503,6 +10587,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -9528,13 +10613,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/jest-runner/node_modules/jest-haste-map": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -9560,6 +10647,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9569,6 +10657,7 @@ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -9602,6 +10691,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -9627,13 +10717,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/jest-runtime/node_modules/jest-haste-map": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -9659,6 +10751,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9668,6 +10761,7 @@ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -9699,6 +10793,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -9724,13 +10819,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/jest-snapshot/node_modules/jest-haste-map": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -9756,6 +10853,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -9782,6 +10880,7 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -9799,6 +10898,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -9811,6 +10911,7 @@ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, + "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -9830,6 +10931,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, + "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -9845,6 +10947,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9856,9 +10959,9 @@ } }, "node_modules/joi": { - "version": "17.12.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", - "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "dev": true, "peer": true, "dependencies": { @@ -9953,6 +11056,8 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", @@ -9998,6 +11103,8 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -10314,6 +11421,7 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -10332,6 +11440,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -10381,7 +11490,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/loader-runner": { "version": "4.3.0", @@ -10681,6 +11791,17 @@ "sourcemap-codec": "^1.4.8" } }, + "node_modules/magicast": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -10707,6 +11828,7 @@ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, + "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -10768,9 +11890,9 @@ } }, "node_modules/metro": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.5.tgz", - "integrity": "sha512-OE/CGbOgbi8BlTN1QqJgKOBaC27dS0JBQw473JcivrpgVnqIsluROA7AavEaTVUrB9wPUZvoNVDROn5uiM2jfw==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.10.tgz", + "integrity": "sha512-FDPi0X7wpafmDREXe1lgg3WzETxtXh6Kpq8+IwsG35R2tMyp2kFIqDdshdohuvDt1J/qDARcEPq7V/jElTb1kA==", "dev": true, "peer": true, "dependencies": { @@ -10788,34 +11910,34 @@ "debug": "^2.2.0", "denodeify": "^1.2.1", "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.18.2", + "hermes-parser": "0.23.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.6.3", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-config": "0.80.5", - "metro-core": "0.80.5", - "metro-file-map": "0.80.5", - "metro-resolver": "0.80.5", - "metro-runtime": "0.80.5", - "metro-source-map": "0.80.5", - "metro-symbolicate": "0.80.5", - "metro-transform-plugins": "0.80.5", - "metro-transform-worker": "0.80.5", + "metro-babel-transformer": "0.80.10", + "metro-cache": "0.80.10", + "metro-cache-key": "0.80.10", + "metro-config": "0.80.10", + "metro-core": "0.80.10", + "metro-file-map": "0.80.10", + "metro-resolver": "0.80.10", + "metro-runtime": "0.80.10", + "metro-source-map": "0.80.10", + "metro-symbolicate": "0.80.10", + "metro-transform-plugins": "0.80.10", + "metro-transform-worker": "0.80.10", "mime-types": "^2.1.27", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", - "rimraf": "^3.0.2", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "strip-ansi": "^6.0.0", "throat": "^5.0.0", - "ws": "^7.5.1", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { @@ -10826,14 +11948,15 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.5.tgz", - "integrity": "sha512-sxH6hcWCorhTbk4kaShCWsadzu99WBL4Nvq4m/sDTbp32//iGuxtAnUK+ZV+6IEygr2u9Z0/4XoZ8Sbcl71MpA==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.10.tgz", + "integrity": "sha512-GXHueUzgzcazfzORDxDzWS9jVVRV6u+cR6TGvHOfGdfLzJCj7/D0PretLfyq+MwN20twHxLW+BUXkoaB8sCQBg==", "dev": true, "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "hermes-parser": "0.18.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.23.0", "nullthrows": "^1.1.1" }, "engines": { @@ -10841,89 +11964,150 @@ } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", + "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", "dev": true, "peer": true }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", + "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", "dev": true, "peer": true, "dependencies": { - "hermes-estree": "0.18.2" + "hermes-estree": "0.23.0" } }, "node_modules/metro-cache": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.5.tgz", - "integrity": "sha512-2u+dQ4PZwmC7eZo9uMBNhQQMig9f+w4QWBZwXCdVy/RYOHM0eObgGdMEOwODo73uxie82T9lWzxr3aZOZ+Nqtw==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.10.tgz", + "integrity": "sha512-8CBtDJwMguIE5RvV3PU1QtxUG8oSSX54mIuAbRZmcQ0MYiOl9JdrMd4JCBvIyhiZLoSStph425SMyCSnjtJsdA==", "dev": true, "peer": true, "dependencies": { - "metro-core": "0.80.5", - "rimraf": "^3.0.2" + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.80.10" }, "engines": { "node": ">=18" } }, "node_modules/metro-cache-key": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.5.tgz", - "integrity": "sha512-fr3QLZUarsB3tRbVcmr34kCBsTHk0Sh9JXGvBY/w3b2lbre+Lq5gtgLyFElHPecGF7o4z1eK9r3ubxtScHWcbA==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.10.tgz", + "integrity": "sha512-57qBhO3zQfoU/hP4ZlLW5hVej2jVfBX6B4NcSfMj4LgDPL3YknWg80IJBxzQfjQY/m+fmMLmPy8aUMHzUp/guA==", "dev": true, "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, "engines": { "node": ">=18" } }, "node_modules/metro-config": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.5.tgz", - "integrity": "sha512-elqo/lwvF+VjZ1OPyvmW/9hSiGlmcqu+rQvDKw5F5WMX48ZC+ySTD1WcaD7e97pkgAlJHVYqZ98FCjRAYOAFRQ==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.10.tgz", + "integrity": "sha512-0GYAw0LkmGbmA81FepKQepL1KU/85Cyv7sAiWm6QWeV6AcVCpsKg6jGLqGHJ0LLPL60rWzA4TV1DQAlzdJAEtA==", "dev": true, "peer": true, "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.6.3", - "metro": "0.80.5", - "metro-cache": "0.80.5", - "metro-core": "0.80.5", - "metro-runtime": "0.80.5" + "metro": "0.80.10", + "metro-cache": "0.80.10", + "metro-core": "0.80.10", + "metro-runtime": "0.80.10" }, "engines": { "node": ">=18" } }, + "node_modules/metro-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "peer": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "peer": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "peer": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/metro-core": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.5.tgz", - "integrity": "sha512-vkLuaBhnZxTVpaZO8ZJVEHzjaqSXpOdpAiztSZ+NDaYM6jEFgle3/XIbLW91jTSf2+T8Pj5yB1G7KuOX+BcVwg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.10.tgz", + "integrity": "sha512-nwBB6HbpGlNsZMuzxVqxqGIOsn5F3JKpsp8PziS7Z4mV8a/jA1d44mVOgYmDa2q5WlH5iJfRIIhdz24XRNDlLA==", "dev": true, "peer": true, "dependencies": { + "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.5" + "metro-resolver": "0.80.10" }, "engines": { "node": ">=18" } }, "node_modules/metro-file-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.5.tgz", - "integrity": "sha512-bKCvJ05drjq6QhQxnDUt3I8x7bTcHo3IIKVobEr14BK++nmxFGn/BmFLRzVBlghM6an3gqwpNEYxS5qNc+VKcg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.10.tgz", + "integrity": "sha512-ytsUq8coneaN7ZCVk1IogojcGhLIbzWyiI2dNmw2nnBgV/0A+M5WaTTgZ6dJEz3dzjObPryDnkqWPvIGLCPtiw==", "dev": true, "peer": true, "dependencies": { "anymatch": "^3.0.3", "debug": "^2.2.0", "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.6.3", @@ -10957,91 +12141,60 @@ "peer": true }, "node_modules/metro-minify-terser": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.5.tgz", - "integrity": "sha512-S7oZLLcab6YXUT6jYFX/ZDMN7Fq6xBGGAG8liMFU1UljX6cTcEC2u+UIafYgCLrdVexp/+ClxrIetVPZ5LtL/g==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.10.tgz", + "integrity": "sha512-Xyv9pEYpOsAerrld7cSLIcnCCpv8ItwysOmTA+AKf1q4KyE9cxrH2O2SA0FzMCkPzwxzBWmXwHUr+A89BpEM6g==", "dev": true, "peer": true, "dependencies": { + "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { "node": ">=18" } }, - "node_modules/metro-minify-terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - }, - "node_modules/metro-minify-terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/metro-minify-terser/node_modules/terser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", - "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "node_modules/metro-resolver": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.10.tgz", + "integrity": "sha512-EYC5CL7f+bSzrqdk1bylKqFNGabfiI5PDctxoPx70jFt89Jz+ThcOscENog8Jb4LEQFG6GkOYlwmPpsi7kx3QA==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" + "flow-enums-runtime": "^0.0.6" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/metro-resolver": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.5.tgz", - "integrity": "sha512-haJ/Hveio3zv/Fr4eXVdKzjUeHHDogYok7OpRqPSXGhTXisNXB+sLN7CpcUrCddFRUDLnVaqQOYwhYsFndgUwA==", - "dev": true, - "peer": true, "engines": { "node": ">=18" } }, "node_modules/metro-runtime": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.5.tgz", - "integrity": "sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.10.tgz", + "integrity": "sha512-Xh0N589ZmSIgJYAM+oYwlzTXEHfASZac9TYPCNbvjNTn0EHKqpoJ/+Im5G3MZT4oZzYv4YnvzRtjqS5k0tK94A==", "dev": true, "peer": true, "dependencies": { - "@babel/runtime": "^7.0.0" + "@babel/runtime": "^7.0.0", + "flow-enums-runtime": "^0.0.6" }, "engines": { "node": ">=18" } }, "node_modules/metro-source-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.5.tgz", - "integrity": "sha512-DwSF4l03mKPNqCtyQ6K23I43qzU1BViAXnuH81eYWdHglP+sDlPpY+/7rUahXEo6qXEHXfAJgVoo1sirbXbmsQ==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.10.tgz", + "integrity": "sha512-EyZswqJW8Uukv/HcQr6K19vkMXW1nzHAZPWJSEyJFKIbgp708QfRZ6vnZGmrtFxeJEaFdNup4bGnu8/mIOYlyA==", "dev": true, "peer": true, "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", + "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.5", + "metro-symbolicate": "0.80.10", "nullthrows": "^1.1.1", - "ob1": "0.80.5", + "ob1": "0.80.10", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -11060,14 +12213,15 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.5.tgz", - "integrity": "sha512-IsM4mTYvmo9JvIqwEkCZ5+YeDVPST78Q17ZgljfLdHLSpIivOHp9oVoiwQ/YGbLx0xRHRIS/tKiXueWBnj3UWA==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.10.tgz", + "integrity": "sha512-qAoVUoSxpfZ2DwZV7IdnQGXCSsf2cAUExUcZyuCqGlY5kaWBb0mx2BL/xbMFDJ4wBp3sVvSBPtK/rt4J7a0xBA==", "dev": true, "peer": true, "dependencies": { + "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.80.5", + "metro-source-map": "0.80.10", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -11091,9 +12245,9 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.5.tgz", - "integrity": "sha512-7IdlTqK/k5+qE3RvIU5QdCJUPk4tHWEqgVuYZu8exeW+s6qOJ66hGIJjXY/P7ccucqF+D4nsbAAW5unkoUdS6g==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.10.tgz", + "integrity": "sha512-leAx9gtA+2MHLsCeWK6XTLBbv2fBnNFu/QiYhWzMq8HsOAP4u1xQAU0tSgPs8+1vYO34Plyn79xTLUtQCRSSUQ==", "dev": true, "peer": true, "dependencies": { @@ -11101,6 +12255,7 @@ "@babel/generator": "^7.20.0", "@babel/template": "^7.0.0", "@babel/traverse": "^7.20.0", + "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" }, "engines": { @@ -11108,9 +12263,9 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.5.tgz", - "integrity": "sha512-Q1oM7hfP+RBgAtzRFBDjPhArELUJF8iRCZ8OidqCpYzQJVGuJZ7InSnIf3hn1JyqiUQwv2f1LXBO78i2rAjzyA==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.10.tgz", + "integrity": "sha512-zNfNLD8Rz99U+JdOTqtF2o7iTjcDMMYdVS90z6+81Tzd2D0lDWVpls7R1hadS6xwM+ymgXFQTjM6V6wFoZaC0g==", "dev": true, "peer": true, "dependencies": { @@ -11118,13 +12273,14 @@ "@babel/generator": "^7.20.0", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", - "metro": "0.80.5", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-minify-terser": "0.80.5", - "metro-source-map": "0.80.5", - "metro-transform-plugins": "0.80.5", + "flow-enums-runtime": "^0.0.6", + "metro": "0.80.10", + "metro-babel-transformer": "0.80.10", + "metro-cache": "0.80.10", + "metro-cache-key": "0.80.10", + "metro-minify-terser": "0.80.10", + "metro-source-map": "0.80.10", + "metro-transform-plugins": "0.80.10", "nullthrows": "^1.1.1" }, "engines": { @@ -11149,20 +12305,20 @@ } }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", + "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", "dev": true, "peer": true }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", + "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", "dev": true, "peer": true, "dependencies": { - "hermes-estree": "0.18.2" + "hermes-estree": "0.23.0" } }, "node_modules/metro/node_modules/ms": { @@ -11255,6 +12411,7 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -11291,6 +12448,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -11382,6 +12548,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -11661,11 +12828,22 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/node-preload": { "version": "0.2.1", @@ -11680,9 +12858,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/node-stream-zip": { @@ -11713,6 +12891,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -11740,7 +12919,9 @@ "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/nyc": { "version": "15.1.0", @@ -11950,11 +13131,14 @@ } }, "node_modules/ob1": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", - "integrity": "sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w==", + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.10.tgz", + "integrity": "sha512-dJHyB0S6JkMorUSfSGcYGkkg9kmq3qDUu3ygZUKIfkr47XOPuG35r2Sk6tbwtHXbdKIXmcMvM8DF2CwgdyaHfQ==", "dev": true, "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, "engines": { "node": ">=18" } @@ -12013,6 +13197,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "peer": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -12153,6 +13338,12 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12170,6 +13361,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -12188,6 +13380,8 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "entities": "^4.4.0" }, @@ -12237,6 +13431,28 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -12255,6 +13471,12 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -12274,9 +13496,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -12306,6 +13528,7 @@ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, + "peer": true, "engines": { "node": ">= 6" } @@ -12374,6 +13597,52 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12412,6 +13681,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, + "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -12426,6 +13696,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -12473,6 +13744,7 @@ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "peer": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -12481,25 +13753,6 @@ "node": ">= 6" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "peer": true - }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -12528,7 +13781,9 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/punycode": { "version": "2.3.0", @@ -12553,12 +13808,14 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "peer": true }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "dev": true, "engines": { "node": ">=0.6.0", @@ -12574,11 +13831,24 @@ "node": ">=0.9" } }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/queue": { "version": "6.0.2", @@ -12656,9 +13926,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "peer": true, "dependencies": { @@ -12669,9 +13939,9 @@ } }, "node_modules/react-devtools-core": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", - "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-5.3.1.tgz", + "integrity": "sha512-7FSb9meX0btdBQLwdFOwt6bGqvRPabmVMMslv8fgoSPqXyuGpgQe36kx8gR86XPw7aV1yVouTp6fyZ0EH+NfUw==", "dev": true, "peer": true, "dependencies": { @@ -12705,34 +13975,35 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "dev": true, + "peer": true }, "node_modules/react-native": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.4.tgz", - "integrity": "sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==", + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.2.tgz", + "integrity": "sha512-pP+Yswd/EurzAlKizytRrid9LJaPJzuNldc+o5t01md2VLHym8V7FWH2z9omFKtFTer8ERg0fAhG1fpd0Qq6bQ==", "dev": true, "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "12.3.2", - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-platform-ios": "12.3.2", - "@react-native/assets-registry": "0.73.1", - "@react-native/codegen": "0.73.3", - "@react-native/community-cli-plugin": "0.73.16", - "@react-native/gradle-plugin": "0.73.4", - "@react-native/js-polyfills": "0.73.1", - "@react-native/normalize-colors": "0.73.2", - "@react-native/virtualized-lists": "0.73.4", + "@react-native-community/cli": "14.0.0", + "@react-native-community/cli-platform-android": "14.0.0", + "@react-native-community/cli-platform-ios": "14.0.0", + "@react-native/assets-registry": "0.75.2", + "@react-native/codegen": "0.75.2", + "@react-native/community-cli-plugin": "0.75.2", + "@react-native/gradle-plugin": "0.75.2", + "@react-native/js-polyfills": "0.75.2", + "@react-native/normalize-colors": "0.75.2", + "@react-native/virtualized-lists": "0.75.2", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "base64-js": "^1.5.1", "chalk": "^4.0.0", - "deprecated-react-native-prop-types": "^5.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", + "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "jsc-android": "^250231.0.0", @@ -12743,11 +14014,11 @@ "nullthrows": "^1.1.1", "pretty-format": "^26.5.2", "promise": "^8.3.0", - "react-devtools-core": "^4.27.7", + "react-devtools-core": "^5.3.1", "react-refresh": "^0.14.0", - "react-shallow-renderer": "^16.15.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.24.0-canary-efb381bbf-20230505", + "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.2", @@ -12760,7 +14031,13 @@ "node": ">=18" }, "peerDependencies": { - "react": "18.2.0" + "@types/react": "^18.2.6", + "react": "^18.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/react-native/node_modules/@jest/types": { @@ -12813,13 +14090,6 @@ "dev": true, "peer": true }, - "node_modules/react-native/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true, - "peer": true - }, "node_modules/react-native/node_modules/ws": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", @@ -12831,29 +14101,15 @@ } }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, "peer": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "dev": true, - "peer": true, - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -12925,9 +14181,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true, "peer": true }, @@ -13037,6 +14293,7 @@ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, + "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -13049,6 +14306,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -13067,6 +14325,7 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, + "peer": true, "engines": { "node": ">=10" } @@ -13105,6 +14364,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -13148,6 +14408,12 @@ "rollup": ">=0.66.0 <3" } }, + "node_modules/rollup-plugin-terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/rollup-plugin-terser/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -13182,6 +14448,23 @@ "node": ">=6" } }, + "node_modules/rollup-plugin-terser/node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/rollup-plugin-typescript2": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", @@ -13308,6 +14591,8 @@ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "xmlchars": "^2.2.0" }, @@ -13343,6 +14628,20 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -13525,6 +14824,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -13561,7 +14866,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/slash": { "version": "3.0.0", @@ -13677,6 +14983,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -13758,6 +15073,7 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -13770,10 +15086,17 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "peer": true, "engines": { "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -13813,6 +15136,12 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -13851,6 +15180,7 @@ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -13873,6 +15203,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -13885,6 +15230,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -13899,6 +15257,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -13957,7 +15316,9 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/tapable": { "version": "2.2.1", @@ -13981,16 +15342,6 @@ "node": ">=6.0.0" } }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/temp-fs": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", @@ -14007,6 +15358,7 @@ "version": "2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.0.5" @@ -14019,6 +15371,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "peer": true, "dependencies": { @@ -14029,20 +15382,21 @@ } }, "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, "node_modules/terser-webpack-plugin": { @@ -14079,12 +15433,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -14108,16 +15456,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/terser-webpack-plugin/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -14133,30 +15471,22 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz", - "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -14248,6 +15578,39 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", + "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -14264,7 +15627,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -14301,6 +15665,8 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "punycode": "^2.1.1" }, @@ -14589,6 +15955,8 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">= 4.0.0" } @@ -14603,9 +15971,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -14622,8 +15990,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -14646,6 +16014,8 @@ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -14692,6 +16062,7 @@ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -14705,7 +16076,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/vary": { "version": "1.1.2", @@ -14716,11706 +16088,527 @@ "node": ">= 0.8" } }, - "node_modules/vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "dev": true, - "peer": true - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "node_modules/vite": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", + "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.41", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=0.10.0" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } } }, - "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "node_modules/vite-node": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", + "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", "dev": true, "dependencies": { - "xml-name-validator": "^4.0.0" + "cac": "^6.7.14", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=14" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "node_modules/vite-node/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { - "makeerror": "1.0.12" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "node_modules/vite/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/vite/node_modules/rollup": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", + "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", "dev": true, "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "peer": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.0", + "@rollup/rollup-android-arm64": "4.21.0", + "@rollup/rollup-darwin-arm64": "4.21.0", + "@rollup/rollup-darwin-x64": "4.21.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", + "@rollup/rollup-linux-arm-musleabihf": "4.21.0", + "@rollup/rollup-linux-arm64-gnu": "4.21.0", + "@rollup/rollup-linux-arm64-musl": "4.21.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", + "@rollup/rollup-linux-riscv64-gnu": "4.21.0", + "@rollup/rollup-linux-s390x-gnu": "4.21.0", + "@rollup/rollup-linux-x64-gnu": "4.21.0", + "@rollup/rollup-linux-x64-musl": "4.21.0", + "@rollup/rollup-win32-arm64-msvc": "4.21.0", + "@rollup/rollup-win32-ia32-msvc": "4.21.0", + "@rollup/rollup-win32-x64-msvc": "4.21.0", + "fsevents": "~2.3.2" } }, - "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" + "node_modules/vitest": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", + "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@vitest/expect": "2.0.5", + "@vitest/pretty-format": "^2.0.5", + "@vitest/runner": "2.0.5", + "@vitest/snapshot": "2.0.5", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "debug": "^4.3.5", + "execa": "^8.0.1", + "magic-string": "^0.30.10", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.8.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.0.5", + "why-is-node-running": "^2.3.0" }, "bin": { - "webpack": "bin/webpack.js" + "vitest": "vitest.mjs" }, "engines": { - "node": ">=10.13.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.0.5", + "@vitest/ui": "2.0.5", + "happy-dom": "*", + "jsdom": "*" }, "peerDependenciesMeta": { - "webpack-cli": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { "optional": true } } }, - "node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "node_modules/vitest/node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "lodash": "^4.17.15" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "node_modules/vitest/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "engines": { - "node": ">=10.13.0" + "node": ">=12" } }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "node_modules/vitest/node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "dependencies": { - "iconv-lite": "0.6.3" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { "node": ">=12" } }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "dev": true, - "peer": true - }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "node_modules/vitest/node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "engines": { - "node": ">=12" + "node": ">= 16" } }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "node_modules/vitest/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/vitest/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, "engines": { - "node": ">= 8" + "node": ">=6" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=16.17" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=16.17.0" } }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "node_modules/vitest/node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", "dev": true, - "peer": true, - "engines": { - "node": ">=0.4" + "dependencies": { + "get-func-name": "^2.0.1" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, - "engines": { - "node": ">=10" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/vitest/node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, - "peer": true, "engines": { - "node": ">= 14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "path-key": "^4.0.0" }, "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs-unparser/node_modules/decamelize": { + "node_modules/vitest/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "node_modules/vitest/node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "engines": { - "node": ">=6" + "node": ">= 14.16" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/vitest/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true - }, - "@babel/core": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", - "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.20", - "@babel/helpers": "^7.22.15", - "@babel/parser": "^7.22.16", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.20", - "@babel/types": "^7.22.19", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, - "requires": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, - "peer": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", - "dev": true, - "peer": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.23.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", - "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true - } - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true - } - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", - "dev": true, - "peer": true, - "requires": { - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "peer": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" - } - }, - "@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" - } - }, - "@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" - } - }, - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", - "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-export-default-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.23.3.tgz", - "integrity": "sha512-Q23MpLZfSGZL1kU7fWqV262q65svLSCIP5kZ/JCW/rKTCm/FrLjpvEd2kfUYMVeHh4QhV/xzyoRAHWrAZJrE3Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-default-from": "^7.23.3" + "url": "https://github.com/sponsors/isaacs" } }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dev": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "peer": true, - "requires": {} - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-default-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.23.3.tgz", - "integrity": "sha512-KeENO5ck1IeZ/l2lFZNy+mpobV3D2Zy5C1YFnWm+YuY5mQiAWc4yAp13dqgguwsBsFVLh4LPCEqCa5qW13N+hw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", - "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", - "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", - "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "peer": true - } - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", - "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-flow": "^7.23.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", - "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", - "dev": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" - } - }, - "@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", - "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz", - "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true - } - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", - "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.23.3" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/preset-env": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", - "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", - "dev": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.9", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.9", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true - } - } - }, - "@babel/preset-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.23.3.tgz", - "integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-flow-strip-types": "^7.23.3" - } - }, - "@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", - "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-typescript": "^7.23.3" - } - }, - "@babel/register": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz", - "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==", - "dev": true, - "peer": true, - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "peer": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "peer": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "peer": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "peer": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "peer": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true, - "peer": true - }, - "@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "dev": true, - "peer": true, - "requires": { - "regenerator-runtime": "^0.14.0" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", - "dev": true - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "dev": true, - "peer": true - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dev": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "dev": true, - "peer": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "requires": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true - }, - "jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - }, - "ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - } - } - }, - "@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", - "dev": true, - "peer": true, - "requires": { - "@jest/types": "^29.6.3" - } - }, - "@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - } - }, - "@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "requires": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - } - }, - "@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3" - } - }, - "@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - } - }, - "@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - } - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - } - } - }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.27.8" - } - }, - "@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "dependencies": { - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - } - } - }, - "@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz", - "integrity": "sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==", - "dev": true, - "requires": { - "merge-options": "^3.0.4" - } - }, - "@react-native-community/cli": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", - "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-clean": "12.3.2", - "@react-native-community/cli-config": "12.3.2", - "@react-native-community/cli-debugger-ui": "12.3.2", - "@react-native-community/cli-doctor": "12.3.2", - "@react-native-community/cli-hermes": "12.3.2", - "@react-native-community/cli-plugin-metro": "12.3.2", - "@react-native-community/cli-server-api": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "@react-native-community/cli-types": "12.3.2", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "dependencies": { - "commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "peer": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "@react-native-community/cli-clean": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", - "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "execa": "^5.0.0" - } - }, - "@react-native-community/cli-config": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", - "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "glob": "^7.1.3", - "joi": "^17.2.1" - } - }, - "@react-native-community/cli-debugger-ui": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", - "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", - "dev": true, - "peer": true, - "requires": { - "serve-static": "^1.13.1" - } - }, - "@react-native-community/cli-doctor": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", - "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-config": "12.3.2", - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-platform-ios": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "@react-native-community/cli-hermes": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", - "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - } - }, - "@react-native-community/cli-platform-android": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", - "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-xml-parser": "^4.2.4", - "glob": "^7.1.3", - "logkitty": "^0.7.1" - } - }, - "@react-native-community/cli-platform-ios": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", - "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "12.3.2", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-xml-parser": "^4.0.12", - "glob": "^7.1.3", - "ora": "^5.4.1" - } - }, - "@react-native-community/cli-plugin-metro": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", - "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==", - "dev": true, - "peer": true - }, - "@react-native-community/cli-server-api": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", - "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-debugger-ui": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "dev": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true - }, - "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "peer": true, - "requires": {} - } - } - }, - "@react-native-community/cli-tools": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", - "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", - "dev": true, - "peer": true, - "requires": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" - } - }, - "@react-native-community/cli-types": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", - "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", - "dev": true, - "peer": true, - "requires": { - "joi": "^17.2.1" - } - }, - "@react-native-community/netinfo": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz", - "integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==", - "dev": true, - "requires": {} - }, - "@react-native/assets-registry": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", - "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", - "dev": true, - "peer": true - }, - "@react-native/babel-plugin-codegen": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", - "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", - "dev": true, - "peer": true, - "requires": { - "@react-native/codegen": "0.73.3" - } - }, - "@react-native/babel-preset": { - "version": "0.73.21", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", - "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", - "dev": true, - "peer": true, - "requires": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.4", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - } - }, - "@react-native/codegen": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", - "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", - "dev": true, - "peer": true, - "requires": { - "@babel/parser": "^7.20.0", - "flow-parser": "^0.206.0", - "glob": "^7.1.1", - "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" - } - }, - "@react-native/community-cli-plugin": { - "version": "0.73.16", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.16.tgz", - "integrity": "sha512-eNH3v3qJJF6f0n/Dck90qfC9gVOR4coAXMTdYECO33GfgjTi+73vf/SBqlXw9HICH/RNZYGPM3wca4FRF7TYeQ==", - "dev": true, - "peer": true, - "requires": { - "@react-native-community/cli-server-api": "12.3.2", - "@react-native-community/cli-tools": "12.3.2", - "@react-native/dev-middleware": "0.73.7", - "@react-native/metro-babel-transformer": "0.73.15", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "readline": "^1.3.0" - } - }, - "@react-native/debugger-frontend": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", - "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", - "dev": true, - "peer": true - }, - "@react-native/dev-middleware": { - "version": "0.73.7", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz", - "integrity": "sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg==", - "dev": true, - "peer": true, - "requires": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.73.3", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^1.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "open": "^7.0.3", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dev": true, - "peer": true, - "requires": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - } - } - } - }, - "@react-native/gradle-plugin": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz", - "integrity": "sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg==", - "dev": true, - "peer": true - }, - "@react-native/js-polyfills": { - "version": "0.73.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", - "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", - "dev": true, - "peer": true - }, - "@react-native/metro-babel-transformer": { - "version": "0.73.15", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", - "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", - "dev": true, - "peer": true, - "requires": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.21", - "hermes-parser": "0.15.0", - "nullthrows": "^1.1.1" - } - }, - "@react-native/normalize-colors": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", - "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", - "dev": true, - "peer": true - }, - "@react-native/virtualized-lists": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", - "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", - "dev": true, - "peer": true, - "requires": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - } - }, - "@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - } - }, - "@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dev": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true, - "peer": true - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true, - "peer": true - }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } - } - }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "optional": true, - "peer": true - }, - "@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@types/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", - "dev": true - }, - "@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "@types/jsdom": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", - "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", - "dev": true - }, - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "@types/nise": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.1.tgz", - "integrity": "sha512-LWDwHYO1C3YPpIQWXHeXAVih2nLsgN1Q5RamkYZRIZYfsz8HGNRji8vNhHs54LjcSgVx6AJC/6n/Q3Tn+fUb3g==", - "dev": true - }, - "@types/node": { - "version": "18.17.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", - "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", - "dev": true - }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/tough-cookie": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", - "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", - "dev": true - }, - "@types/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", - "dev": true - }, - "@types/uuid": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", - "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "peer": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true - }, - "acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "requires": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, - "acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "requires": {} - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "dev": true, - "peer": true - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "dev": true, - "peer": true, - "requires": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "dev": true, - "peer": true - }, - "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "requires": { - "default-require-extensions": "^3.0.0" - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "peer": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "ast-types": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", - "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.0.1" - } - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "peer": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true, - "peer": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true, - "peer": true, - "requires": {} - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", - "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", - "dev": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.5.0", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", - "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.5.0", - "core-js-compat": "^3.34.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", - "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.5.0" - } - }, - "babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/plugin-syntax-flow": "^7.12.1" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "peer": true - }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "peer": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - } - }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - }, - "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - } - } - }, - "browserstack-local": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.4.tgz", - "integrity": "sha512-OueHCaQQutO+Fezg+ZTieRn+gdV+JocLjiAQ8nYecu08GhIt3ms79cDHfpoZmECAdoQ6OLdm7ODd+DtQzl4lrA==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "peer": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "dev": true, - "peer": true, - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "dev": true, - "peer": true - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "dev": true, - "peer": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001585", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", - "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", - "dev": true - }, - "chai": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", - "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "chromium-edge-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", - "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "peer": true - } - } - }, - "ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "peer": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "peer": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "peer": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "peer": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true, - "peer": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true, - "peer": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "peer": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "peer": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "peer": true - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true - }, - "core-js-compat": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", - "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", - "dev": true, - "peer": true, - "requires": { - "browserslist": "^4.22.2" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "peer": true - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "peer": true, - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "dependencies": { - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "dev": true, - "peer": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "peer": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true, - "peer": true - } - } - }, - "coveralls-next": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", - "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", - "dev": true, - "requires": { - "form-data": "4.0.0", - "js-yaml": "4.1.0", - "lcov-parse": "1.0.0", - "log-driver": "1.2.7", - "minimist": "1.2.7" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true - } - } - }, - "create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "dependencies": { - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "requires": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true - }, - "jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - }, - "ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - } - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - } - }, - "date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "dev": true - }, - "dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "dev": true, - "peer": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "decompress-response": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", - "integrity": "sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, - "requires": {} - }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, - "default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "requires": { - "strip-bom": "^4.0.0" - } - }, - "defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "peer": true, - "requires": { - "clone": "^1.0.2" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "dev": true, - "peer": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "deprecated-react-native-prop-types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", - "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", - "dev": true, - "peer": true, - "requires": { - "@react-native/normalize-colors": "^0.73.0", - "invariant": "^2.2.4", - "prop-types": "^15.8.1" - } - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "requires": { - "webidl-conversions": "^7.0.0" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.661", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.661.tgz", - "integrity": "sha512-AFg4wDHSOk5F+zA8aR+SVIOabu7m0e7BiJnigCvPXzIGy731XENw/lmNxTySpVFtkFEy+eyt4oHhh5FF3NjQNw==", - "dev": true - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true - }, - "engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", - "dev": true, - "requires": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - } - }, - "engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true - }, - "envinfo": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", - "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", - "dev": true, - "peer": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dev": true, - "peer": true, - "requires": { - "stackframe": "^1.3.4" - } - }, - "errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "dev": true, - "peer": true, - "requires": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - } - }, - "es-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", - "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", - "dev": true - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", - "@humanwhocodes/config-array": "^0.11.11", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "peer": true - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "peer": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fast-xml-parser": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", - "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", - "dev": true, - "peer": true, - "requires": { - "strnum": "^1.0.5" - } - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true - } - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "dependencies": { - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", - "dev": true, - "requires": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "flow-enums-runtime": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", - "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "dev": true, - "peer": true - }, - "flow-parser": { - "version": "0.206.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", - "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", - "dev": true, - "peer": true - }, - "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true - }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", - "dev": true, - "requires": { - "samsam": "1.x" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "peer": true - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, - "fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true - }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "dependencies": { - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hermes-estree": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", - "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", - "dev": true, - "peer": true - }, - "hermes-parser": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", - "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", - "dev": true, - "peer": true, - "requires": { - "hermes-estree": "0.15.0" - } - }, - "hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "dev": true, - "peer": true, - "requires": { - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "peer": true - } - } - }, - "html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "requires": { - "whatwg-encoding": "^2.0.0" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "peer": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "image-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", - "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", - "dev": true, - "peer": true, - "requires": { - "queue": "6.0.2" - } - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "dev": true, - "peer": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "dev": true, - "peer": true - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "peer": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "peer": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "peer": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "peer": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "peer": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "requires": { - "append-transform": "^2.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - } - }, - "jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "dependencies": { - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "requires": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true - }, - "jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - }, - "ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - } - } - }, - "jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - } - }, - "jest-environment-jsdom": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", - "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0", - "jsdom": "^20.0.0" - } - }, - "jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true - }, - "jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-localstorage-mock": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz", - "integrity": "sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==", - "dev": true - }, - "jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "dependencies": { - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - } - } - }, - "jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "dependencies": { - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - } - } - }, - "jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - } - } - }, - "jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - } - } - }, - "jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "dependencies": { - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - } - } - }, - "jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "joi": { - "version": "17.12.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", - "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", - "dev": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsc-android": { - "version": "250231.0.0", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", - "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", - "dev": true, - "peer": true - }, - "jsc-safe-url": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", - "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "dev": true, - "peer": true - }, - "jscodeshift": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", - "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", - "dev": true, - "peer": true, - "requires": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.21.0", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "dependencies": { - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, - "jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "dependencies": { - "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "peer": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "karma": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", - "integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "dependencies": { - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "ua-parser-js": { - "version": "0.7.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", - "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } - }, - "karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", - "dev": true, - "requires": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" - } - }, - "karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true, - "requires": {} - }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "karma-mocha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", - "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", - "dev": true, - "requires": { - "minimist": "^1.2.3" - } - }, - "karma-webpack": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", - "integrity": "sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^9.0.3", - "webpack-merge": "^4.1.5" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "peer": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "dev": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - } - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "peer": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "dev": true, - "peer": true - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", - "dev": true, - "requires": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - } - }, - "logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "dev": true, - "peer": true, - "requires": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "dependencies": { - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "peer": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "peer": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "requires": { - "semver": "^7.5.3" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "dev": true, - "peer": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true - }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "dev": true, - "peer": true - }, - "merge-options": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "dev": true, - "requires": { - "is-plain-obj": "^2.1.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "metro": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.5.tgz", - "integrity": "sha512-OE/CGbOgbi8BlTN1QqJgKOBaC27dS0JBQw473JcivrpgVnqIsluROA7AavEaTVUrB9wPUZvoNVDROn5uiM2jfw==", - "dev": true, - "peer": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "accepts": "^1.3.7", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "error-stack-parser": "^2.0.6", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.18.2", - "image-size": "^1.0.2", - "invariant": "^2.2.4", - "jest-worker": "^29.6.3", - "jsc-safe-url": "^0.2.2", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-config": "0.80.5", - "metro-core": "0.80.5", - "metro-file-map": "0.80.5", - "metro-resolver": "0.80.5", - "metro-runtime": "0.80.5", - "metro-source-map": "0.80.5", - "metro-symbolicate": "0.80.5", - "metro-transform-plugins": "0.80.5", - "metro-transform-worker": "0.80.5", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "rimraf": "^3.0.2", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", - "throat": "^5.0.0", - "ws": "^7.5.1", - "yargs": "^17.6.2" - }, - "dependencies": { - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", - "dev": true, - "peer": true - }, - "hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", - "dev": true, - "peer": true, - "requires": { - "hermes-estree": "0.18.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "peer": true - }, - "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "peer": true, - "requires": {} - } - } - }, - "metro-babel-transformer": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.5.tgz", - "integrity": "sha512-sxH6hcWCorhTbk4kaShCWsadzu99WBL4Nvq4m/sDTbp32//iGuxtAnUK+ZV+6IEygr2u9Z0/4XoZ8Sbcl71MpA==", - "dev": true, - "peer": true, - "requires": { - "@babel/core": "^7.20.0", - "hermes-parser": "0.18.2", - "nullthrows": "^1.1.1" - }, - "dependencies": { - "hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==", - "dev": true, - "peer": true - }, - "hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", - "dev": true, - "peer": true, - "requires": { - "hermes-estree": "0.18.2" - } - } - } - }, - "metro-cache": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.5.tgz", - "integrity": "sha512-2u+dQ4PZwmC7eZo9uMBNhQQMig9f+w4QWBZwXCdVy/RYOHM0eObgGdMEOwODo73uxie82T9lWzxr3aZOZ+Nqtw==", - "dev": true, - "peer": true, - "requires": { - "metro-core": "0.80.5", - "rimraf": "^3.0.2" - } - }, - "metro-cache-key": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.5.tgz", - "integrity": "sha512-fr3QLZUarsB3tRbVcmr34kCBsTHk0Sh9JXGvBY/w3b2lbre+Lq5gtgLyFElHPecGF7o4z1eK9r3ubxtScHWcbA==", - "dev": true, - "peer": true - }, - "metro-config": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.5.tgz", - "integrity": "sha512-elqo/lwvF+VjZ1OPyvmW/9hSiGlmcqu+rQvDKw5F5WMX48ZC+ySTD1WcaD7e97pkgAlJHVYqZ98FCjRAYOAFRQ==", - "dev": true, - "peer": true, - "requires": { - "connect": "^3.6.5", - "cosmiconfig": "^5.0.5", - "jest-validate": "^29.6.3", - "metro": "0.80.5", - "metro-cache": "0.80.5", - "metro-core": "0.80.5", - "metro-runtime": "0.80.5" - } - }, - "metro-core": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.5.tgz", - "integrity": "sha512-vkLuaBhnZxTVpaZO8ZJVEHzjaqSXpOdpAiztSZ+NDaYM6jEFgle3/XIbLW91jTSf2+T8Pj5yB1G7KuOX+BcVwg==", - "dev": true, - "peer": true, - "requires": { - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.5" - } - }, - "metro-file-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.5.tgz", - "integrity": "sha512-bKCvJ05drjq6QhQxnDUt3I8x7bTcHo3IIKVobEr14BK++nmxFGn/BmFLRzVBlghM6an3gqwpNEYxS5qNc+VKcg==", - "dev": true, - "peer": true, - "requires": { - "anymatch": "^3.0.3", - "debug": "^2.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "invariant": "^2.2.4", - "jest-worker": "^29.6.3", - "micromatch": "^4.0.4", - "node-abort-controller": "^3.1.1", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - } - } - }, - "metro-minify-terser": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.5.tgz", - "integrity": "sha512-S7oZLLcab6YXUT6jYFX/ZDMN7Fq6xBGGAG8liMFU1UljX6cTcEC2u+UIafYgCLrdVexp/+ClxrIetVPZ5LtL/g==", - "dev": true, - "peer": true, - "requires": { - "terser": "^5.15.0" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "terser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", - "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - } - } - }, - "metro-resolver": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.5.tgz", - "integrity": "sha512-haJ/Hveio3zv/Fr4eXVdKzjUeHHDogYok7OpRqPSXGhTXisNXB+sLN7CpcUrCddFRUDLnVaqQOYwhYsFndgUwA==", - "dev": true, - "peer": true - }, - "metro-runtime": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.5.tgz", - "integrity": "sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg==", - "dev": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.0.0" - } - }, - "metro-source-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.5.tgz", - "integrity": "sha512-DwSF4l03mKPNqCtyQ6K23I43qzU1BViAXnuH81eYWdHglP+sDlPpY+/7rUahXEo6qXEHXfAJgVoo1sirbXbmsQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.80.5", - "nullthrows": "^1.1.1", - "ob1": "0.80.5", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "peer": true - } - } - }, - "metro-symbolicate": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.5.tgz", - "integrity": "sha512-IsM4mTYvmo9JvIqwEkCZ5+YeDVPST78Q17ZgljfLdHLSpIivOHp9oVoiwQ/YGbLx0xRHRIS/tKiXueWBnj3UWA==", - "dev": true, - "peer": true, - "requires": { - "invariant": "^2.2.4", - "metro-source-map": "0.80.5", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.1", - "vlq": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "peer": true - } - } - }, - "metro-transform-plugins": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.5.tgz", - "integrity": "sha512-7IdlTqK/k5+qE3RvIU5QdCJUPk4tHWEqgVuYZu8exeW+s6qOJ66hGIJjXY/P7ccucqF+D4nsbAAW5unkoUdS6g==", - "dev": true, - "peer": true, - "requires": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "nullthrows": "^1.1.1" - } - }, - "metro-transform-worker": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.5.tgz", - "integrity": "sha512-Q1oM7hfP+RBgAtzRFBDjPhArELUJF8iRCZ8OidqCpYzQJVGuJZ7InSnIf3hn1JyqiUQwv2f1LXBO78i2rAjzyA==", - "dev": true, - "peer": true, - "requires": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "metro": "0.80.5", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-minify-terser": "0.80.5", - "metro-source-map": "0.80.5", - "metro-transform-plugins": "0.80.5", - "nullthrows": "^1.1.1" - } - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - } - } - }, - "mocha-lcov-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "murmurhash": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", - "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" - }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - } - }, - "nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "dev": true, - "peer": true - }, - "nock": { - "version": "11.9.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", - "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.13", - "mkdirp": "^0.5.0", - "propagate": "^2.0.0" - } - }, - "node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true, - "peer": true - }, - "node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "dev": true, - "peer": true, - "requires": { - "minimatch": "^3.0.2" - } - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "peer": true, - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "peer": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "peer": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "peer": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "requires": { - "process-on-spawn": "^1.0.0" - } - }, - "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "dev": true, - "peer": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", - "dev": true - }, - "nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "dev": true, - "peer": true - }, - "nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true - }, - "nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "dependencies": { - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "ob1": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", - "integrity": "sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w==", - "dev": true, - "peer": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "peer": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "dev": true, - "peer": true, - "requires": { - "is-wsl": "^1.1.0" - }, - "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true, - "peer": true - } - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "peer": true, - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "requires": { - "entities": "^4.4.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", - "dev": true, - "requires": { - "through": "~2.3" - } - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "peer": true - }, - "pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "peer": true - }, - "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "requires": { - "fromentries": "^1.2.0" - } - }, - "promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dev": true, - "peer": true, - "requires": { - "asap": "~2.0.6" - } - }, - "promise-polyfill": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", - "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "peer": true - } - } - }, - "propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true - }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "requires": { - "event-stream": "=3.3.4" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - }, - "pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "dev": true - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "~2.0.3" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-devtools-core": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", - "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", - "dev": true, - "peer": true, - "requires": { - "shell-quote": "^1.6.1", - "ws": "^7" - }, - "dependencies": { - "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "peer": true, - "requires": {} - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "react-native": { - "version": "0.73.4", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.4.tgz", - "integrity": "sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==", - "dev": true, - "peer": true, - "requires": { - "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "12.3.2", - "@react-native-community/cli-platform-android": "12.3.2", - "@react-native-community/cli-platform-ios": "12.3.2", - "@react-native/assets-registry": "0.73.1", - "@react-native/codegen": "0.73.3", - "@react-native/community-cli-plugin": "0.73.16", - "@react-native/gradle-plugin": "0.73.4", - "@react-native/js-polyfills": "0.73.1", - "@react-native/normalize-colors": "0.73.2", - "@react-native/virtualized-lists": "0.73.4", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "ansi-regex": "^5.0.0", - "base64-js": "^1.5.1", - "chalk": "^4.0.0", - "deprecated-react-native-prop-types": "^5.0.0", - "event-target-shim": "^5.0.1", - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "jest-environment-node": "^29.6.3", - "jsc-android": "^250231.0.0", - "memoize-one": "^5.0.0", - "metro-runtime": "^0.80.3", - "metro-source-map": "^0.80.3", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", - "promise": "^8.3.0", - "react-devtools-core": "^4.27.7", - "react-refresh": "^0.14.0", - "react-shallow-renderer": "^16.15.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "0.24.0-canary-efb381bbf-20230505", - "stacktrace-parser": "^0.1.10", - "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2", - "yargs": "^17.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "dev": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true, - "peer": true - }, - "ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "dev": true, - "peer": true, - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dev": true, - "peer": true - }, - "react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "dev": true, - "peer": true, - "requires": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "dev": true, - "peer": true - }, - "recast": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", - "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", - "dev": true, - "peer": true, - "requires": { - "ast-types": "0.15.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "peer": true - }, - "regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "dev": true, - "peer": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, - "peer": true - }, - "regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, - "peer": true, - "requires": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "peer": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "peer": true - } - } - }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "peer": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", - "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", - "dev": true, - "requires": { - "fsevents": "~2.1.2" - }, - "dependencies": { - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - } - } - }, - "rollup-plugin-terser": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", - "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "jest-worker": "^24.9.0", - "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^4.0.0", - "terser": "^4.6.2" - }, - "dependencies": { - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "rollup-plugin-typescript2": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", - "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "8.1.0", - "resolve": "1.17.0", - "tslib": "2.0.1" - }, - "dependencies": { - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", - "dev": true - } - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - } - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, - "saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scheduler": { - "version": "0.24.0-canary-efb381bbf-20230505", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", - "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "peer": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "peer": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "peer": true - } - } - }, - "serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "dev": true, - "peer": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "peer": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "peer": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "peer": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", - "dev": true, - "requires": { - "diff": "^3.1.0", - "formatio": "1.2.0", - "lolex": "^1.6.0", - "native-promise-only": "^0.8.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" - }, - "dependencies": { - "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", - "dev": true - } - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true - } - } - }, - "socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.5.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - } - }, - "socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "dev": true, - "requires": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dev": true, - "requires": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "requires": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "dependencies": { - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dev": true, - "requires": { - "through": "2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "dev": true, - "peer": true - }, - "stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "peer": true, - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "dev": true, - "peer": true - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, - "streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "dev": true, - "requires": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "dev": true, - "peer": true - }, - "sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, - "temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "dev": true, - "peer": true, - "requires": { - "rimraf": "~2.6.2" - }, - "dependencies": { - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "dev": true, - "peer": true - }, - "temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", - "dev": true, - "requires": { - "rimraf": "~2.5.2" - }, - "dependencies": { - "rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - } - } - }, - "terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "terser": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz", - "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true, - "peer": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "peer": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "peer": true - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "peer": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - } - }, - "ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - } - }, - "ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, - "ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true - }, - "ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "peer": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "peer": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "peer": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "peer": true - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "peer": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true - }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, - "v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "engines": { + "node": ">=12" }, - "dependencies": { - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true - }, - "vlq": { + "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", "dev": true, "peer": true }, - "void-elements": { + "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "w3c-xmlserializer": { + "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, - "requires": { + "optional": true, + "peer": true, + "dependencies": { "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" } }, - "walker": { + "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "requires": { + "peer": true, + "dependencies": { "makeerror": "1.0.12" } }, - "watchpack": { + "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, - "requires": { + "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" } }, - "wcwidth": { + "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, "peer": true, - "requires": { + "dependencies": { "defaults": "^1.0.3" } }, - "webidl-conversions": { + "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true + "dev": true, + "engines": { + "node": ">=12" + } }, - "webpack": { + "node_modules/webpack": { "version": "5.88.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "dev": true, - "requires": { + "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", "@webassemblyjs/ast": "^1.11.5", @@ -26441,161 +16634,274 @@ "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, - "dependencies": { - "@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true } } }, - "webpack-merge": { + "node_modules/webpack-merge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", "dev": true, - "requires": { + "dependencies": { "lodash": "^4.17.15" } }, - "webpack-sources": { + "node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, - "whatwg-encoding": { + "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", "dev": true, - "requires": { + "optional": true, + "peer": true, + "dependencies": { "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" } }, - "whatwg-fetch": { + "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "dev": true, "peer": true }, - "whatwg-mimetype": { + "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=12" + } }, - "whatwg-url": { + "node_modules/whatwg-url": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, - "requires": { + "optional": true, + "peer": true, + "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "which-module": { + "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, - "workerpool": { + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, - "wrap-ansi": { + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "write-file-atomic": { + "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "requires": { + "peer": true, + "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "ws": { + "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, - "requires": {} + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, - "xml-name-validator": { + "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } }, - "xmlchars": { + "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, - "xtend": { + "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, - "peer": true + "peer": true, + "engines": { + "node": ">=0.4" + } }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "yallist": { + "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "node_modules/yaml": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "dev": true, - "peer": true + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } }, - "yargs": { + "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "requires": { + "peer": true, + "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", @@ -26603,51 +16909,79 @@ "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "yargs-parser": { + "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true + "dev": true, + "engines": { + "node": ">=12" + } }, - "yargs-unparser": { + "node_modules/yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, - "requires": { + "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - } + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "yn": { + "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index b651ac20d..2d2e09618 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,9 @@ "clean": "rm -rf dist", "clean:win": "(if exist dist rd /s/q dist)", "lint": "tsc --noEmit && eslint 'lib/**/*.js' 'lib/**/*.ts'", - "test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js' && jest", + "test-vitest": "tsc --noEmit --p tsconfig.spec.json && vitest run", + "test-mocha": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js'", + "test": "npm run test-mocha && npm run test-vitest", "posttest": "npm run lint", "test-ci": "npm run test-xbrowser && npm run test-umdbrowser", "test-xbrowser": "karma start karma.bs.conf.js --single-run", @@ -116,7 +118,6 @@ "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/chai": "^4.2.11", - "@types/jest": "^29.5.12", "@types/mocha": "^5.2.7", "@types/nise": "^1.4.0", "@types/node": "^18.7.18", @@ -124,14 +125,13 @@ "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", + "@vitest/coverage-istanbul": "^2.0.5", "chai": "^4.2.0", "coveralls-next": "^4.2.0", "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.0.0", - "jest-localstorage-mock": "^2.4.22", + "happy-dom": "^14.12.3", "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", @@ -157,6 +157,7 @@ "ts-node": "^8.10.2", "tslib": "^2.4.0", "typescript": "^4.7.4", + "vitest": "^2.0.5", "webpack": "^5.74.0" }, "peerDependencies": { diff --git a/tests/backoffController.spec.ts b/tests/backoffController.spec.ts index ff37a1e73..846ac0c52 100644 --- a/tests/backoffController.spec.ts +++ b/tests/backoffController.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,8 @@ * limitations under the License. */ +import { describe, it, expect } from 'vitest'; + import BackoffController from '../lib/modules/datafile-manager/backoffController'; describe('backoffController', () => { diff --git a/tests/browserAsyncStorageCache.spec.ts b/tests/browserAsyncStorageCache.spec.ts index ca5417b09..c30b675bc 100644 --- a/tests/browserAsyncStorageCache.spec.ts +++ b/tests/browserAsyncStorageCache.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022,2024, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, it, expect, vi } from 'vitest'; import BrowserAsyncStorageCache from '../lib/plugins/key_value_cache/browserAsyncStorageCache'; @@ -31,13 +31,13 @@ describe('BrowserAsyncStorageCache', () => { cacheInstance = new BrowserAsyncStorageCache(); - jest + vi .spyOn(localStorage, 'getItem') .mockImplementation((key) => key == KEY_THAT_EXISTS ? VALUE_FOR_KEY_THAT_EXISTS : null); - jest + vi .spyOn(localStorage, 'setItem') .mockImplementation(() => 1); - jest + vi .spyOn(localStorage, 'removeItem') .mockImplementation((key) => key == KEY_THAT_EXISTS); }); diff --git a/tests/browserDatafileManager.spec.ts b/tests/browserDatafileManager.spec.ts index bfd1adb1c..d643b2cb3 100644 --- a/tests/browserDatafileManager.spec.ts +++ b/tests/browserDatafileManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,26 +14,28 @@ * limitations under the License. */ +import { describe, beforeEach, afterEach, it, expect, vi, MockInstance } from 'vitest'; + import BrowserDatafileManager from '../lib/modules/datafile-manager/browserDatafileManager'; import * as browserRequest from '../lib/modules/datafile-manager/browserRequest'; import { Headers, AbortableRequest } from '../lib/modules/datafile-manager/http'; import { advanceTimersByTime, getTimerCount } from './testUtils'; describe('browserDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; + let makeGetRequestSpy: MockInstance<(reqUrl: string, headers: Headers) => AbortableRequest>; beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(browserRequest, 'makeGetRequest'); + vi.useFakeTimers(); + makeGetRequestSpy = vi.spyOn(browserRequest, 'makeGetRequest'); }); afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); + vi.restoreAllMocks(); + vi.clearAllTimers(); }); it('calls makeGetRequest when started', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -56,7 +58,7 @@ describe('browserDatafileManager', () => { it('calls makeGetRequest for live update requests', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -83,7 +85,7 @@ describe('browserDatafileManager', () => { it('defaults to false for autoUpdate', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', diff --git a/tests/browserRequest.spec.ts b/tests/browserRequest.spec.ts index 24ab30dd9..42a52329f 100644 --- a/tests/browserRequest.spec.ts +++ b/tests/browserRequest.spec.ts @@ -2,7 +2,7 @@ * @jest-environment jsdom */ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ * limitations under the License. */ +import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest'; + import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; import { makeGetRequest } from '../lib/modules/datafile-manager/browserRequest'; @@ -122,7 +124,7 @@ describe('browserRequest', () => { }); it('sets a timeout on the request object', () => { - const onCreateMock = jest.fn(); + const onCreateMock = vi.fn(); mockXHR.onCreate = onCreateMock; makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); expect(onCreateMock).toBeCalledTimes(1); diff --git a/tests/browserRequestHandler.spec.ts b/tests/browserRequestHandler.spec.ts index 24624bebe..763bba54e 100644 --- a/tests/browserRequestHandler.spec.ts +++ b/tests/browserRequestHandler.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; @@ -132,7 +132,7 @@ describe('BrowserRequestHandler', () => { it('should set a timeout on the request object', () => { const timeout = 60000; - const onCreateMock = jest.fn(); + const onCreateMock = vi.fn(); mockXHR.onCreate = onCreateMock; new BrowserRequestHandler(new NoOpLogger(), timeout).makeRequest(host, {}, 'get'); diff --git a/tests/buildEventV1.spec.ts b/tests/buildEventV1.spec.ts index 273bcea6e..7f8f56008 100644 --- a/tests/buildEventV1.spec.ts +++ b/tests/buildEventV1.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, it, expect } from 'vitest'; import { buildConversionEventV1, diff --git a/tests/eventEmitter.spec.ts b/tests/eventEmitter.spec.ts index bd1d83ebf..16e91b83e 100644 --- a/tests/eventEmitter.spec.ts +++ b/tests/eventEmitter.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { expect, vi, it, beforeEach, describe } from 'vitest'; import EventEmitter from '../lib/modules/datafile-manager/eventEmitter'; describe('event_emitter', () => { @@ -24,21 +24,21 @@ describe('event_emitter', () => { }); it('can add a listener for the update event', () => { - const listener = jest.fn(); + const listener = vi.fn(); emitter.on('update', listener); emitter.emit('update', { datafile: 'abcd' }); expect(listener).toBeCalledTimes(1); }); it('passes the argument from emit to the listener', () => { - const listener = jest.fn(); + const listener = vi.fn(); emitter.on('update', listener); emitter.emit('update', { datafile: 'abcd' }); expect(listener).toBeCalledWith({ datafile: 'abcd' }); }); it('returns a dispose function that removes the listener', () => { - const listener = jest.fn(); + const listener = vi.fn(); const disposer = emitter.on('update', listener); disposer(); emitter.emit('update', { datafile: 'efgh' }); @@ -46,9 +46,9 @@ describe('event_emitter', () => { }); it('can add several listeners for the update event', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); + const listener1 = vi.fn(); + const listener2 = vi.fn(); + const listener3 = vi.fn(); emitter.on('update', listener1); emitter.on('update', listener2); emitter.on('update', listener3); @@ -59,9 +59,9 @@ describe('event_emitter', () => { }); it('can add several listeners and remove only some of them', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); + const listener1 = vi.fn(); + const listener2 = vi.fn(); + const listener3 = vi.fn(); const disposer1 = emitter.on('update', listener1); const disposer2 = emitter.on('update', listener2); emitter.on('update', listener3); @@ -78,8 +78,8 @@ describe('event_emitter', () => { }); it('can add listeners for different events and remove only some of them', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); + const readyListener = vi.fn(); + const updateListener = vi.fn(); const readyDisposer = emitter.on('ready', readyListener); const updateDisposer = emitter.on('update', updateListener); emitter.emit('ready'); @@ -102,8 +102,8 @@ describe('event_emitter', () => { }); it('can remove all listeners', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); + const readyListener = vi.fn(); + const updateListener = vi.fn(); emitter.on('ready', readyListener); emitter.on('update', updateListener); emitter.removeAllListeners(); diff --git a/tests/eventQueue.spec.ts b/tests/eventQueue.spec.ts index 0ebf82454..0a9e5fae2 100644 --- a/tests/eventQueue.spec.ts +++ b/tests/eventQueue.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import { DefaultEventQueue, SingleEventQueue } from '../lib/modules/event_processor/eventQueue' describe('eventQueue', () => { beforeEach(() => { - jest.useFakeTimers() + vi.useFakeTimers() }) afterEach(() => { - jest.useRealTimers() - jest.resetAllMocks() + vi.useRealTimers() + vi.resetAllMocks() }) describe('SingleEventQueue', () => { it('should immediately invoke the sink function when items are enqueued', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new SingleEventQueue<number>({ sink: sinkFn, }) @@ -51,7 +51,7 @@ describe('eventQueue', () => { describe('DefaultEventQueue', () => { it('should treat maxQueueSize = -1 as 1', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: -1, @@ -72,7 +72,7 @@ describe('eventQueue', () => { }) it('should treat maxQueueSize = 0 as 1', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: 0, @@ -93,7 +93,7 @@ describe('eventQueue', () => { }) it('should invoke the sink function when maxQueueSize is reached', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: 3, @@ -121,7 +121,7 @@ describe('eventQueue', () => { }) it('should invoke the sink function when the interval has expired', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: 100, @@ -135,13 +135,13 @@ describe('eventQueue', () => { queue.enqueue(2) expect(sinkFn).not.toHaveBeenCalled() - jest.advanceTimersByTime(100) + vi.advanceTimersByTime(100) expect(sinkFn).toHaveBeenCalledTimes(1) expect(sinkFn).toHaveBeenCalledWith([1, 2]) queue.enqueue(3) - jest.advanceTimersByTime(100) + vi.advanceTimersByTime(100) expect(sinkFn).toHaveBeenCalledTimes(2) expect(sinkFn).toHaveBeenCalledWith([3]) @@ -150,7 +150,7 @@ describe('eventQueue', () => { }) it('should invoke the sink function when an item incompatable with the current batch (according to batchComparator) is received', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<string>({ flushInterval: 100, maxQueueSize: 100, @@ -174,7 +174,7 @@ describe('eventQueue', () => { }) it('stop() should flush the existing queue and call timer.stop()', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: 100, @@ -182,7 +182,7 @@ describe('eventQueue', () => { batchComparator: () => true }) - jest.spyOn(queue.timer, 'stop') + vi.spyOn(queue.timer, 'stop') queue.start() queue.enqueue(1) @@ -198,7 +198,7 @@ describe('eventQueue', () => { }) it('flush() should clear the current batch', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: 100, @@ -206,7 +206,7 @@ describe('eventQueue', () => { batchComparator: () => true }) - jest.spyOn(queue.timer, 'refresh') + vi.spyOn(queue.timer, 'refresh') queue.start() queue.enqueue(1) @@ -221,7 +221,7 @@ describe('eventQueue', () => { it('stop() should return a promise', () => { const promise = Promise.resolve() - const sinkFn = jest.fn().mockReturnValue(promise) + const sinkFn = vi.fn().mockReturnValue(promise) const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: 100, @@ -233,7 +233,7 @@ describe('eventQueue', () => { }) it('should start the timer when the first event is put into the queue', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 100, maxQueueSize: 100, @@ -242,24 +242,24 @@ describe('eventQueue', () => { }) queue.start() - jest.advanceTimersByTime(99) + vi.advanceTimersByTime(99) queue.enqueue(1) - jest.advanceTimersByTime(2) + vi.advanceTimersByTime(2) expect(sinkFn).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(98) + vi.advanceTimersByTime(98) expect(sinkFn).toHaveBeenCalledTimes(1) expect(sinkFn).toHaveBeenCalledWith([1]) - jest.advanceTimersByTime(500) + vi.advanceTimersByTime(500) // ensure sink function wasnt called again since no events have // been added expect(sinkFn).toHaveBeenCalledTimes(1) queue.enqueue(2) - jest.advanceTimersByTime(100) + vi.advanceTimersByTime(100) expect(sinkFn).toHaveBeenCalledTimes(2) expect(sinkFn).toHaveBeenLastCalledWith([2]) @@ -268,7 +268,7 @@ describe('eventQueue', () => { }) it('should not enqueue additional events after stop() is called', () => { - const sinkFn = jest.fn() + const sinkFn = vi.fn() const queue = new DefaultEventQueue<number>({ flushInterval: 30000, maxQueueSize: 3, diff --git a/tests/httpPollingDatafileManager.spec.ts b/tests/httpPollingDatafileManager.spec.ts index 64f7317ac..201fe0eae 100644 --- a/tests/httpPollingDatafileManager.spec.ts +++ b/tests/httpPollingDatafileManager.spec.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, afterEach, beforeAll, it, expect, vi, MockInstance } from 'vitest'; import HttpPollingDatafileManager from '../lib/modules/datafile-manager/httpPollingDatafileManager'; import { Headers, AbortableRequest, Response } from '../lib/modules/datafile-manager/http'; @@ -21,17 +22,18 @@ import { advanceTimersByTime, getTimerCount } from './testUtils'; import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; -jest.mock('../lib/modules/datafile-manager/backoffController', () => { - return jest.fn().mockImplementation(() => { - const getDelayMock = jest.fn().mockImplementation(() => 0); - return { - getDelay: getDelayMock, - countError: jest.fn(), - reset: jest.fn(), - }; - }); +vi.mock('../lib/modules/datafile-manager/backoffController', () => { + const MockBackoffController = vi.fn(); + MockBackoffController.prototype.getDelay = vi.fn().mockImplementation(() => 0); + MockBackoffController.prototype.countError = vi.fn(); + MockBackoffController.prototype.reset = vi.fn(); + + return { + 'default': MockBackoffController, + } }); + import BackoffController from '../lib/modules/datafile-manager/backoffController'; import { LoggerFacade, getLogger } from '../lib/modules/logging'; import { resetCalls, spy, verify } from 'ts-mockito'; @@ -63,7 +65,7 @@ export class TestDatafileManager extends HttpPollingDatafileManager { } } this.responsePromises.push(responsePromise); - return { responsePromise, abort: jest.fn() }; + return { responsePromise, abort: vi.fn() }; } getConfigDefaults(): Partial<DatafileManagerConfig> { @@ -107,7 +109,7 @@ describe('httpPollingDatafileManager', () => { }); beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); resetCalls(spiedLogger); }); @@ -116,9 +118,9 @@ describe('httpPollingDatafileManager', () => { if (manager) { manager.stop(); } - jest.clearAllMocks(); - jest.restoreAllMocks(); - jest.clearAllTimers(); + vi.clearAllMocks(); + vi.restoreAllMocks(); + vi.clearAllTimers(); }); describe('when constructed with sdkKey and datafile and autoUpdate: true,', () => { @@ -143,7 +145,7 @@ describe('httpPollingDatafileManager', () => { headers: {}, } ); - const updateFn = jest.fn(); + const updateFn = vi.fn(); manager.on('update', updateFn); manager.start(); expect(manager.responsePromises.length).toBe(1); @@ -217,7 +219,7 @@ describe('httpPollingDatafileManager', () => { describe('started state', () => { it('passes the default datafile URL to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); manager.queuedResponses.push({ statusCode: 200, body: '{"foo": "bar"}', @@ -254,7 +256,7 @@ describe('httpPollingDatafileManager', () => { headers: {}, } ); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); manager.start(); expect(makeGetRequestSpy).toBeCalledTimes(1); await manager.responsePromises[0]; @@ -281,7 +283,7 @@ describe('httpPollingDatafileManager', () => { } ); - const updateFn = jest.fn(); + const updateFn = vi.fn(); manager.on('update', updateFn); manager.start(); @@ -310,7 +312,7 @@ describe('httpPollingDatafileManager', () => { const responsePromise: Promise<Response> = new Promise(res => { resolveResponsePromise = res; }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest').mockReturnValueOnce({ + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest').mockReturnValueOnce({ abort() {}, responsePromise, }); @@ -380,7 +382,7 @@ describe('httpPollingDatafileManager', () => { body: '{"foo2": "bar2"}', headers: {}, }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); manager.start(); const currentRequest = makeGetRequestSpy.mock.results[0]; // @ts-ignore @@ -432,7 +434,7 @@ describe('httpPollingDatafileManager', () => { } ); - const updateFn = jest.fn(); + const updateFn = vi.fn(); manager.on('update', updateFn); manager.start(); @@ -467,7 +469,7 @@ describe('httpPollingDatafileManager', () => { ); manager.start(); await manager.onReady(); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); await advanceTimersByTime(1000); expect(makeGetRequestSpy).toBeCalledTimes(1); const firstCall = makeGetRequestSpy.mock.calls[0]; @@ -480,11 +482,11 @@ describe('httpPollingDatafileManager', () => { describe('backoff', () => { it('uses the delay from the backoff controller getDelay method when greater than updateInterval', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; + const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; const getDelayMock = BackoffControllerMock.mock.results[0].value.getDelay; getDelayMock.mockImplementationOnce(() => 5432); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); manager.queuedResponses.push({ statusCode: 404, @@ -512,7 +514,7 @@ describe('httpPollingDatafileManager', () => { }); manager.start(); await manager.responsePromises[0]; - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; + const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); }); @@ -524,7 +526,7 @@ describe('httpPollingDatafileManager', () => { } catch (e) { //empty } - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; + const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); }); @@ -537,7 +539,7 @@ describe('httpPollingDatafileManager', () => { }, }); manager.start(); - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; + const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; // Reset is called in start - we want to check that it is also called after the response, so reset the mock here BackoffControllerMock.mock.results[0].value.reset.mockReset(); await manager.onReady(); @@ -545,7 +547,7 @@ describe('httpPollingDatafileManager', () => { }); it('resets the backoff controller when start is called', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; + const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; manager.start(); expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); try { @@ -581,7 +583,7 @@ describe('httpPollingDatafileManager', () => { body: '{"foo": "bar"}', headers: {}, }); - const updateFn = jest.fn(); + const updateFn = vi.fn(); manager.on('update', updateFn); manager.start(); await manager.onReady(); @@ -620,7 +622,7 @@ describe('httpPollingDatafileManager', () => { }); it('uses the urlTemplate to create the url passed to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); manager.queuedResponses.push({ statusCode: 200, body: '{"foo": "bar"}', @@ -639,7 +641,7 @@ describe('httpPollingDatafileManager', () => { }); it('uses the default update interval', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); + const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); manager.queuedResponses.push({ statusCode: 200, @@ -668,7 +670,7 @@ describe('httpPollingDatafileManager', () => { it('uses cached version of datafile first and resolves the promise while network throws error and no update event is triggered', async () => { manager.queuedResponses.push(new Error('Connection Error')); - const updateFn = jest.fn(); + const updateFn = vi.fn(); manager.on('update', updateFn); manager.start(); await manager.onReady(); @@ -685,7 +687,7 @@ describe('httpPollingDatafileManager', () => { headers: {}, }); - const updateFn = jest.fn(); + const updateFn = vi.fn(); manager.on('update', updateFn); manager.start(); await manager.onReady(); @@ -697,7 +699,7 @@ describe('httpPollingDatafileManager', () => { }); it('sets newly recieved datafile in to cache', async () => { - const cacheSetSpy = jest.spyOn(testCache, 'set'); + const cacheSetSpy = vi.spyOn(testCache, 'set'); manager.queuedResponses.push({ statusCode: 200, body: '{"foo": "bar"}', @@ -730,7 +732,7 @@ describe('httpPollingDatafileManager', () => { headers: {}, }); - const updateFn = jest.fn(); + const updateFn = vi.fn(); manager.on('update', updateFn); manager.start(); await advanceTimersByTime(50); diff --git a/tests/httpPollingDatafileManagerPolling.spec.ts b/tests/httpPollingDatafileManagerPolling.spec.ts index a5be36eed..f1e57b864 100644 --- a/tests/httpPollingDatafileManagerPolling.spec.ts +++ b/tests/httpPollingDatafileManagerPolling.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023 Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, afterEach, beforeAll, it, expect, vi, MockInstance } from 'vitest'; import { resetCalls, spy, verify } from 'ts-mockito'; import { LogLevel, LoggerFacade, getLogger, setLogLevel } from '../lib/modules/logging'; diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index a11f40c32..425b4d1cb 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; + import * as logging from '../lib/modules/logging/logger'; import * as eventProcessor from '../lib//plugins/event_processor/index.react_native'; @@ -24,17 +25,18 @@ import optimizelyFactory from '../lib/index.react_native'; import configValidator from '../lib/utils/config_validator'; import eventProcessorConfigValidator from '../lib/utils/event_processor_config_validator'; -jest.mock('react-native-get-random-values') -jest.mock('fast-text-encoding') +vi.mock('@react-native-community/netinfo'); +vi.mock('react-native-get-random-values') +vi.mock('fast-text-encoding') describe('javascript-sdk/react-native', () => { beforeEach(() => { - jest.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent'); - jest.useFakeTimers(); + vi.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent'); + vi.useFakeTimers(); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); describe('APIs', () => { @@ -56,14 +58,14 @@ describe('javascript-sdk/react-native', () => { beforeEach(() => { // @ts-ignore silentLogger = optimizelyFactory.logging.createLogger(); - jest.spyOn(console, 'error'); - jest.spyOn(configValidator, 'validate').mockImplementation(() => { + vi.spyOn(console, 'error'); + vi.spyOn(configValidator, 'validate').mockImplementation(() => { throw new Error('Invalid config or something'); }); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should not throw if the provided config is not valid', () => { @@ -131,11 +133,11 @@ describe('javascript-sdk/react-native', () => { describe('when passing in logLevel', () => { beforeEach(() => { - jest.spyOn(logging, 'setLogLevel'); + vi.spyOn(logging, 'setLogLevel'); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should call logging.setLogLevel', () => { @@ -150,11 +152,11 @@ describe('javascript-sdk/react-native', () => { describe('when passing in logger', () => { beforeEach(() => { - jest.spyOn(logging, 'setLogHandler'); + vi.spyOn(logging, 'setLogHandler'); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should call logging.setLogHandler with the supplied logger', () => { @@ -173,11 +175,11 @@ describe('javascript-sdk/react-native', () => { // @ts-ignore let eventProcessorSpy; beforeEach(() => { - eventProcessorSpy = jest.spyOn(eventProcessor, 'createEventProcessor'); + eventProcessorSpy = vi.spyOn(eventProcessor, 'createEventProcessor'); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should use default event flush interval when none is provided', () => { @@ -201,11 +203,11 @@ describe('javascript-sdk/react-native', () => { describe('with an invalid flush interval', () => { beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); + vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should ignore the event flush interval and use the default instead', () => { @@ -231,11 +233,11 @@ describe('javascript-sdk/react-native', () => { describe('with a valid flush interval', () => { beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); + vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should use the provided event flush interval', () => { @@ -278,11 +280,11 @@ describe('javascript-sdk/react-native', () => { describe('with an invalid event batch size', () => { beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); + vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should ignore the event batch size and use the default instead', () => { @@ -308,11 +310,11 @@ describe('javascript-sdk/react-native', () => { describe('with a valid event batch size', () => { beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); + vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should use the provided event batch size', () => { diff --git a/tests/jsconfig.json b/tests/jsconfig.json deleted file mode 100644 index 594d9e97d..000000000 --- a/tests/jsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "typeAcquisition": { - "include": [ - "jest" - ] - } -} diff --git a/tests/logger.spec.ts b/tests/logger.spec.ts index f34afbb0e..17d5cc38b 100644 --- a/tests/logger.spec.ts +++ b/tests/logger.spec.ts @@ -1,4 +1,5 @@ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; + import { LogLevel, LogHandler, @@ -30,10 +31,10 @@ describe('logger', () => { beforeEach(() => { stubLogger = { - log: jest.fn(), + log: vi.fn(), } stubErrorHandler = { - handleError: jest.fn(), + handleError: vi.fn(), } setLogLevel(LogLevel.DEBUG) setLogHandler(stubLogger) @@ -272,11 +273,11 @@ describe('logger', () => { describe('using ConsoleLoggerHandler', () => { beforeEach(() => { - jest.spyOn(console, 'info').mockImplementation(() => {}) + vi.spyOn(console, 'info').mockImplementation(() => {}) }) afterEach(() => { - jest.resetAllMocks() + vi.resetAllMocks() }) it('should work with BasicLogger', () => { @@ -284,7 +285,7 @@ describe('logger', () => { const TIME = '12:00' setLogHandler(logger) setLogLevel(LogLevel.INFO) - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) logger.log(LogLevel.INFO, 'hey') @@ -311,14 +312,14 @@ describe('logger', () => { describe('ConsoleLogger', function() { beforeEach(() => { - jest.spyOn(console, 'info') - jest.spyOn(console, 'log') - jest.spyOn(console, 'warn') - jest.spyOn(console, 'error') + vi.spyOn(console, 'info') + vi.spyOn(console, 'log') + vi.spyOn(console, 'warn') + vi.spyOn(console, 'error') }) afterEach(() => { - jest.resetAllMocks() + vi.resetAllMocks() }) it('should log to console.info for LogLevel.INFO', () => { @@ -326,7 +327,7 @@ describe('logger', () => { logLevel: LogLevel.DEBUG, }) const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) logger.log(LogLevel.INFO, 'test') @@ -339,7 +340,7 @@ describe('logger', () => { logLevel: LogLevel.DEBUG, }) const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) logger.log(LogLevel.DEBUG, 'debug') @@ -352,7 +353,7 @@ describe('logger', () => { logLevel: LogLevel.DEBUG, }) const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) logger.log(LogLevel.WARNING, 'warning') @@ -365,7 +366,7 @@ describe('logger', () => { logLevel: LogLevel.DEBUG, }) const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) + vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) logger.log(LogLevel.ERROR, 'error') diff --git a/tests/nodeDatafileManager.spec.ts b/tests/nodeDatafileManager.spec.ts index 14fb49d05..11217663c 100644 --- a/tests/nodeDatafileManager.spec.ts +++ b/tests/nodeDatafileManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, afterEach, beforeAll, it, expect, vi, MockInstance } from 'vitest'; import NodeDatafileManager from '../lib/modules/datafile-manager/nodeDatafileManager'; import * as nodeRequest from '../lib/modules/datafile-manager/nodeRequest'; @@ -20,20 +21,20 @@ import { Headers, AbortableRequest } from '../lib/modules/datafile-manager/http' import { advanceTimersByTime, getTimerCount } from './testUtils'; describe('nodeDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; + let makeGetRequestSpy: MockInstance<(reqUrl: string, headers: Headers) => AbortableRequest>; beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(nodeRequest, 'makeGetRequest'); + vi.useFakeTimers(); + makeGetRequestSpy = vi.spyOn(nodeRequest, 'makeGetRequest'); }); afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); + vi.restoreAllMocks(); + vi.clearAllTimers(); }); it('calls nodeEnvironment.makeGetRequest when started', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -56,7 +57,7 @@ describe('nodeDatafileManager', () => { it('calls nodeEnvironment.makeGetRequest for live update requests', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -83,7 +84,7 @@ describe('nodeDatafileManager', () => { it('defaults to true for autoUpdate', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -107,7 +108,7 @@ describe('nodeDatafileManager', () => { it('uses authenticated default datafile url when auth token is provided', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -129,7 +130,7 @@ describe('nodeDatafileManager', () => { it('uses public default datafile url when auth token is not provided', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -147,7 +148,7 @@ describe('nodeDatafileManager', () => { it('adds authorization header with bearer token when auth token is provided', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', @@ -166,7 +167,7 @@ describe('nodeDatafileManager', () => { it('prefers user provided url template over defaults', async () => { makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), + abort: vi.fn(), responsePromise: Promise.resolve({ statusCode: 200, body: '{"foo":"bar"}', diff --git a/tests/nodeRequest.spec.ts b/tests/nodeRequest.spec.ts index ed5e0f0b2..8f1c66c8e 100644 --- a/tests/nodeRequest.spec.ts +++ b/tests/nodeRequest.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, afterEach, beforeAll, afterAll, it, vi, expect } from 'vitest'; import nock from 'nock'; import zlib from 'zlib'; @@ -179,11 +180,11 @@ describe('nodeEnvironment', () => { describe('timeout', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); + vi.clearAllTimers(); }); it('rejects the response promise and aborts the request when the response is not received before the timeout', async () => { @@ -192,7 +193,7 @@ describe('nodeEnvironment', () => { .delay(61000) .reply(200, '{"foo":"bar"}'); - const abortEventListener = jest.fn(); + const abortEventListener = vi.fn(); let emittedReq: any; const requestListener = (request: any): void => { emittedReq = request; diff --git a/tests/nodeRequestHandler.spec.ts b/tests/nodeRequestHandler.spec.ts index 149cc7270..06c2e2bac 100644 --- a/tests/nodeRequestHandler.spec.ts +++ b/tests/nodeRequestHandler.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, beforeAll, afterAll, it, vi, expect } from 'vitest'; import nock from 'nock'; import zlib from 'zlib'; @@ -195,20 +195,20 @@ describe('NodeRequestHandler', () => { describe('timeout', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); + vi.clearAllTimers(); }); - it.only('should reject the response promise and abort the request when the response is not received before the timeout', async () => { + it('should reject the response promise and abort the request when the response is not received before the timeout', async () => { const scope = nock(host) .get(path) .delay({ head: 2000, body: 2000 }) .reply(200, body); - const abortEventListener = jest.fn(); + const abortEventListener = vi.fn(); // eslint-disable-next-line @typescript-eslint/no-explicit-any let emittedReq: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -220,9 +220,9 @@ describe('NodeRequestHandler', () => { const request = new NodeRequestHandler(new NoOpLogger(), 100).makeRequest(`${host}${path}`, {}, 'get'); - jest.advanceTimersByTime(60000); - jest.runAllTimers(); // <- explicitly tell jest to run all setTimeout, setInterval - jest.runAllTicks(); // <- explicitly tell jest to run all Promise callback + vi.advanceTimersByTime(60000); + vi.runAllTimers(); // <- explicitly tell vi to run all setTimeout, setInterval + vi.runAllTicks(); // <- explicitly tell vi to run all Promise callback await expect(request.responsePromise).rejects.toThrow(); expect(abortEventListener).toBeCalledTimes(1); diff --git a/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts index c989b76a6..518b1b07c 100644 --- a/tests/odpEventApiManager.spec.ts +++ b/tests/odpEventApiManager.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; import { anyString, anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 31bc7a753..ebd3b1838 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, afterEach, beforeAll, it, vi, expect } from 'vitest'; import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE, ERROR_MESSAGES } from '../lib/utils/enums'; import { OdpConfig } from '../lib/core/odp/odp_config'; @@ -26,7 +27,8 @@ import { LogHandler, LogLevel } from '../lib/modules/logging'; import { OdpEvent } from '../lib/core/odp/odp_event'; import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; -import exp from 'constants'; +import { resolve } from 'path'; +import { advanceTimersByTime } from './testUtils'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; @@ -168,11 +170,15 @@ describe('OdpEventManager', () => { }); beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); resetCalls(mockLogger); resetCalls(mockApiManager); }); + afterEach(() => { + vi.clearAllTimers(); + }); + it('should log an error and not start if start() is called without a config', () => { const eventManager = new TestOdpEventManager({ odpConfig: undefined, @@ -305,11 +311,11 @@ describe('OdpEventManager', () => { }); //@ts-ignore - const processQueueSpy = jest.spyOn(eventManager, 'processQueue'); + const processQueueSpy = vi.spyOn(eventManager, 'processQueue'); eventManager.start(); // do not add events to the queue, but allow for... - jest.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) + vi.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) expect(processQueueSpy).toHaveBeenCalledTimes(3); }); @@ -327,13 +333,13 @@ describe('OdpEventManager', () => { }); //@ts-ignore - const processQueueSpy = jest.spyOn(eventManager, 'processQueue'); + const processQueueSpy = vi.spyOn(eventManager, 'processQueue'); eventManager.start(); eventManager.sendEvent(EVENTS[0]); eventManager.sendEvent(EVENTS[1]); - jest.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) + vi.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) expect(processQueueSpy).toHaveBeenCalledTimes(2); }); @@ -358,14 +364,15 @@ describe('OdpEventManager', () => { eventManager.sendEvent(makeEvent(i)); } - jest.runAllTicks(); - // as we are not advancing the jest fake timers, no flush should occur + await Promise.resolve(); + + // as we are not advancing the vi fake timers, no flush should occur // ...there should be 3 batches: // batch #1 with 10, batch #2 with 10, and batch #3 (after flushInterval lapsed) with 5 = 25 events verify(mockApiManager.sendEvents(anything(), anything())).twice(); // rest of the events should now be flushed - jest.advanceTimersByTime(250); + await advanceTimersByTime(250); verify(mockApiManager.sendEvents(anything(), anything())).thrice(); }); @@ -383,7 +390,7 @@ describe('OdpEventManager', () => { eventManager.start(); EVENTS.forEach(event => eventManager.sendEvent(event)); - jest.advanceTimersByTime(100); + await advanceTimersByTime(100); // sending 1 batch of 2 events after flushInterval since batchSize is 10 verify(mockApiManager.sendEvents(anything(), anything())).once(); const [_, events] = capture(mockApiManager.sendEvents).last(); @@ -408,7 +415,7 @@ describe('OdpEventManager', () => { eventManager.start(); EVENTS.forEach(event => eventManager.sendEvent(event)); - jest.advanceTimersByTime(100); + await advanceTimersByTime(100); // sending 1 batch of 2 events after flushInterval since batchSize is 10 verify(mockApiManager.sendEvents(anything(), anything())).once(); @@ -439,7 +446,7 @@ describe('OdpEventManager', () => { eventManager.start(); EVENTS.forEach(event => eventManager.sendEvent(event)); - jest.advanceTimersByTime(100); + await advanceTimersByTime(100); verify(mockApiManager.sendEvents(anything(), anything())).called(); const [_, events] = capture(mockApiManager.sendEvents).last(); @@ -472,8 +479,8 @@ describe('OdpEventManager', () => { eventManager.sendEvent(makeEvent(i)); } - jest.runAllTicks(); - jest.useRealTimers(); + vi.runAllTicks(); + vi.useRealTimers(); await pause(100); // retry 3x for 2 batches or 6 calls to attempt to process @@ -502,8 +509,8 @@ describe('OdpEventManager', () => { expect(eventManager.getQueue().length).toEqual(25); eventManager.flush(); - - jest.runAllTicks(); + + await Promise.resolve(); verify(mockApiManager.sendEvents(anything(), anything())).once(); expect(eventManager.getQueue().length).toEqual(0); @@ -531,7 +538,7 @@ describe('OdpEventManager', () => { eventManager.flush(); - jest.runAllTicks(); + await Promise.resolve(); verify(mockApiManager.sendEvents(anything(), anything())).once(); expect(eventManager.getQueue().length).toEqual(0); @@ -563,7 +570,7 @@ describe('OdpEventManager', () => { eventManager.updateSettings(updatedConfig); - jest.runAllTicks(); + await Promise.resolve(); verify(mockApiManager.sendEvents(anything(), anything())).once(); expect(eventManager.getQueue().length).toEqual(0); @@ -595,20 +602,19 @@ describe('OdpEventManager', () => { expect(eventManager.getQueue().length).toEqual(25); - jest.advanceTimersByTime(100); + await advanceTimersByTime(100); expect(eventManager.getQueue().length).toEqual(0); let [usedOdpConfig] = capture(mockApiManager.sendEvents).first(); expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); eventManager.updateSettings(updatedConfig); - jest.runAllTicks(); - for (let i = 0; i < 25; i += 1) { eventManager.sendEvent(makeEvent(i)); } - jest.advanceTimersByTime(100); + + await advanceTimersByTime(100); expect(eventManager.getQueue().length).toEqual(0); ([usedOdpConfig] = capture(mockApiManager.sendEvents).last()); @@ -636,7 +642,7 @@ describe('OdpEventManager', () => { eventManager.start(); eventManager.registerVuid(vuid); - jest.advanceTimersByTime(250); + await advanceTimersByTime(250); const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toBe(1); @@ -672,7 +678,7 @@ describe('OdpEventManager', () => { eventManager.start(); eventManager.identifyUser(fsUserId, vuid); - jest.advanceTimersByTime(250); + await advanceTimersByTime(260); const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toBe(1); @@ -701,7 +707,7 @@ describe('OdpEventManager', () => { eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); eventManager.stop(); - jest.runAllTicks(); + vi.runAllTicks(); verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).twice(); }); @@ -720,7 +726,7 @@ describe('OdpEventManager', () => { eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); eventManager.stop(); - jest.runAllTicks(); + vi.runAllTicks(); verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).never(); }); diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index b9ecb76f0..2622b5c4d 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, beforeAll, it, vi, expect } from 'vitest'; import { anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 90228cc52..009f9997b 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, beforeAll, it, vi, expect } from 'vitest'; import { anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; -import { LOG_MESSAGES } from './../lib/utils/enums/index'; import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; import { LogHandler, LogLevel } from '../lib/modules/logging'; @@ -138,7 +137,7 @@ describe('OdpManager', () => { }); it('should call initialzeVuid on construction if vuid is enabled', () => { - const vuidInitializer = jest.fn(); + const vuidInitializer = vi.fn(); const odpManager = testOdpManager({ segmentManager, @@ -576,7 +575,7 @@ describe('OdpManager', () => { verify(mockEventManager.sendEvent(anything())).never(); }); - it.only('should fetch qualified segments correctly for both fs_user_id and vuid', async () => { + it('should fetch qualified segments correctly for both fs_user_id and vuid', async () => { const userId = 'user123'; const vuid = 'vuid_123'; @@ -681,7 +680,7 @@ describe('OdpManager', () => { expect(segments).toBeNull(); odpManager.identifyUser('vuid_user1'); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).twice(); + verify(mockLogger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).once(); verify(mockEventManager.identifyUser(anything(), anything())).never(); const identifiers = new Map([['email', 'a@b.com']]); @@ -694,7 +693,7 @@ describe('OdpManager', () => { data, }); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).thrice(); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).twice(); verify(mockEventManager.sendEvent(anything())).never(); }); }); diff --git a/tests/odpSegmentApiManager.spec.ts b/tests/odpSegmentApiManager.spec.ts index 2d155e73f..bcc82f698 100644 --- a/tests/odpSegmentApiManager.spec.ts +++ b/tests/odpSegmentApiManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts index f4421175f..723b40cd7 100644 --- a/tests/odpSegmentManager.spec.ts +++ b/tests/odpSegmentManager.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, it, expect } from 'vitest'; import { mock, resetCalls, instance } from 'ts-mockito'; diff --git a/tests/pendingEventsDispatcher.spec.ts b/tests/pendingEventsDispatcher.spec.ts index 214b1e939..153edae5e 100644 --- a/tests/pendingEventsDispatcher.spec.ts +++ b/tests/pendingEventsDispatcher.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> - -jest.mock('../lib/utils/fns', () => ({ - __esModule: true, - uuid: jest.fn(), - getTimestamp: jest.fn(), - objectValues: jest.requireActual('../lib/utils/fns').objectValues, -})) +import { describe, beforeEach, afterEach, it, expect, vi, MockInstance } from 'vitest'; + +vi.mock('../lib/utils/fns', async (importOriginal) => { + const actual: any = await importOriginal(); + return { + __esModule: true, + uuid: vi.fn(), + getTimestamp: vi.fn(), + objectValues: actual.objectValues, + } +}); import { LocalStoragePendingEventsDispatcher, @@ -38,13 +41,13 @@ describe('LocalStoragePendingEventsDispatcher', () => { beforeEach(() => { originalEventDispatcher = { - dispatchEvent: jest.fn(), + dispatchEvent: vi.fn(), } pendingEventsDispatcher = new LocalStoragePendingEventsDispatcher({ eventDispatcher: originalEventDispatcher, }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((uuid as unknown) as jest.Mock).mockReturnValue('uuid') + ;((getTimestamp as unknown) as MockInstance).mockReturnValue(1) + ;((uuid as unknown) as MockInstance).mockReturnValue('uuid') }) afterEach(() => { @@ -52,7 +55,7 @@ describe('LocalStoragePendingEventsDispatcher', () => { }) it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', () => { - const callback = jest.fn() + const callback = vi.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', @@ -63,12 +66,12 @@ describe('LocalStoragePendingEventsDispatcher', () => { expect(callback).not.toHaveBeenCalled() // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] internalDispatchCall[1]({ statusCode: 200 }) // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) + expect((originalEventDispatcher.dispatchEvent as unknown) as MockInstance).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) // assert that the passed in callback to pendingEventsDispatcher was called @@ -77,7 +80,7 @@ describe('LocalStoragePendingEventsDispatcher', () => { }) it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => { - const callback = jest.fn() + const callback = vi.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', @@ -88,12 +91,12 @@ describe('LocalStoragePendingEventsDispatcher', () => { expect(callback).not.toHaveBeenCalled() // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] internalDispatchCall[1]({ statusCode: 400 }) // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) + expect((originalEventDispatcher.dispatchEvent as unknown) as MockInstance).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) // assert that the passed in callback to pendingEventsDispatcher was called @@ -109,7 +112,7 @@ describe('PendingEventsDispatcher', () => { beforeEach(() => { originalEventDispatcher = { - dispatchEvent: jest.fn(), + dispatchEvent: vi.fn(), } store = new LocalStorageStore({ key: 'test', @@ -118,9 +121,9 @@ describe('PendingEventsDispatcher', () => { pendingEventsDispatcher = new PendingEventsDispatcher({ store, eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((uuid as unknown) as jest.Mock).mockReturnValue('uuid') + }); + ((getTimestamp as unknown) as MockInstance).mockReturnValue(1); + ((uuid as unknown) as MockInstance).mockReturnValue('uuid'); }) afterEach(() => { @@ -130,7 +133,7 @@ describe('PendingEventsDispatcher', () => { describe('dispatch', () => { describe('when the dispatch is successful', () => { it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() + const callback = vi.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', @@ -148,13 +151,13 @@ describe('PendingEventsDispatcher', () => { expect(callback).not.toHaveBeenCalled() // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] const internalCallback = internalDispatchCall[1]({ statusCode: 200 }) // assert that the original dispatch function was called with the request expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, + (originalEventDispatcher.dispatchEvent as unknown) as MockInstance, ).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) @@ -168,7 +171,7 @@ describe('PendingEventsDispatcher', () => { describe('when the dispatch is unsuccessful', () => { it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() + const callback = vi.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', @@ -186,13 +189,13 @@ describe('PendingEventsDispatcher', () => { expect(callback).not.toHaveBeenCalled() // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] internalDispatchCall[1]({ statusCode: 400 }) // assert that the original dispatch function was called with the request expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, + (originalEventDispatcher.dispatchEvent as unknown) as MockInstance, ).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) @@ -219,7 +222,7 @@ describe('PendingEventsDispatcher', () => { it('should dispatch all of the pending events, and remove them from store', () => { expect(store.values()).toHaveLength(0) - const callback = jest.fn() + const callback = vi.fn() const eventV1Request1: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', @@ -249,7 +252,7 @@ describe('PendingEventsDispatcher', () => { expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) // manually invoke original eventDispatcher callback - const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls internalDispatchCalls[0][1]({ statusCode: 200 }) internalDispatchCalls[1][1]({ statusCode: 200 }) diff --git a/tests/pendingEventsStore.spec.ts b/tests/pendingEventsStore.spec.ts index 9e8062429..9a3fff864 100644 --- a/tests/pendingEventsStore.spec.ts +++ b/tests/pendingEventsStore.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, expect, vi, MockInstance } from 'vitest'; import { LocalStorageStore } from '../lib/modules/event_processor/pendingEventsStore' diff --git a/tests/reactNativeAsyncStorageCache.spec.ts b/tests/reactNativeAsyncStorageCache.spec.ts index 2a26635ac..a7d1a936e 100644 --- a/tests/reactNativeAsyncStorageCache.spec.ts +++ b/tests/reactNativeAsyncStorageCache.spec.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, beforeAll, it, vi, expect } from 'vitest'; + +vi.mock('@react-native-async-storage/async-storage'); import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'; diff --git a/tests/reactNativeDatafileManger.spec.ts b/tests/reactNativeDatafileManager.spec.ts similarity index 69% rename from tests/reactNativeDatafileManger.spec.ts rename to tests/reactNativeDatafileManager.spec.ts index 45a23f0b0..2a3c354f4 100644 --- a/tests/reactNativeDatafileManger.spec.ts +++ b/tests/reactNativeDatafileManager.spec.ts @@ -14,39 +14,45 @@ * limitations under the License. */ -const mockGet = jest.fn().mockImplementation((key: string): Promise<string | undefined> => { - let val = undefined; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); -}); +import { describe, beforeEach, afterEach, it, vi, expect, MockedObject } from 'vitest'; -const mockSet = jest.fn().mockImplementation((): Promise<void> => { - return Promise.resolve(); -}); +const { mockMap, mockGet, mockSet, mockRemove, mockContains } = vi.hoisted(() => { + const mockMap = new Map(); -const mockContains = jest.fn().mockImplementation((): Promise<boolean> => { - return Promise.resolve(false); -}); + const mockGet = vi.fn().mockImplementation((key) => { + return Promise.resolve(mockMap.get(key)); + }); -const mockRemove = jest.fn().mockImplementation((): Promise<boolean> => { - return Promise.resolve(false); -}); + const mockSet = vi.fn().mockImplementation((key, value) => { + mockMap.set(key, value); + return Promise.resolve(); + }); -jest.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { - return jest.fn().mockImplementation(() => { - return { - get: mockGet, - set: mockSet, - contains: mockContains, - remove: mockRemove, + const mockRemove = vi.fn().mockImplementation((key) => { + if (mockMap.has(key)) { + mockMap.delete(key); + return Promise.resolve(true); } + return Promise.resolve(false); + }); + + const mockContains = vi.fn().mockImplementation((key) => { + return Promise.resolve(mockMap.has(key)); }); + + return { mockMap, mockGet, mockSet, mockRemove, mockContains }; +}); + +vi.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { + const MockReactNativeAsyncStorageCache = vi.fn(); + MockReactNativeAsyncStorageCache.prototype.get = mockGet; + MockReactNativeAsyncStorageCache.prototype.set = mockSet; + MockReactNativeAsyncStorageCache.prototype.contains = mockContains; + MockReactNativeAsyncStorageCache.prototype.remove = mockRemove; + return { 'default': MockReactNativeAsyncStorageCache }; }); + import { advanceTimersByTime } from './testUtils'; import ReactNativeDatafileManager from '../lib/modules/datafile-manager/reactNativeDatafileManager'; import { Headers, AbortableRequest, Response } from '../lib/modules/datafile-manager/http'; @@ -76,12 +82,12 @@ class MockRequestReactNativeDatafileManager extends ReactNativeDatafileManager { } } this.responsePromises.push(responsePromise); - return { responsePromise, abort: jest.fn() }; + return { responsePromise, abort: vi.fn() }; } } describe('reactNativeDatafileManager', () => { - const MockedReactNativeAsyncStorageCache = jest.mocked(ReactNativeAsyncStorageCache); + const MockedReactNativeAsyncStorageCache = vi.mocked(ReactNativeAsyncStorageCache); const testCache: PersistentKeyValueCache = { get(key: string): Promise<string | undefined> { @@ -108,12 +114,12 @@ describe('reactNativeDatafileManager', () => { }; beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); + vi.clearAllTimers(); + vi.useRealTimers(); MockedReactNativeAsyncStorageCache.mockClear(); mockGet.mockClear(); mockSet.mockClear(); @@ -122,18 +128,13 @@ describe('reactNativeDatafileManager', () => { }); it('uses the user provided cache', async () => { - const cache = { - get: mockGet, - set: mockSet, - contains: mockContains, - remove: mockRemove, - }; - + const setSpy = vi.spyOn(testCache, 'set'); + const manager = new MockRequestReactNativeDatafileManager({ sdkKey: 'keyThatExists', updateInterval: 500, autoUpdate: true, - cache, + cache: testCache, }); manager.simulateResponseDelay = true; @@ -143,12 +144,13 @@ describe('reactNativeDatafileManager', () => { body: '{"foo": "bar"}', headers: {}, }); + manager.start(); + vi.advanceTimersByTime(50); await manager.onReady(); - await advanceTimersByTime(50); expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(mockSet.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); - expect(JSON.parse(mockSet.mock.calls[0][1])).toEqual({ foo: 'bar' }); + expect(setSpy.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); + expect(JSON.parse(setSpy.mock.calls[0][1])).toEqual({ foo: 'bar' }); }); it('uses ReactNativeAsyncStorageCache if no cache is provided', async () => { @@ -166,8 +168,8 @@ describe('reactNativeDatafileManager', () => { }); manager.start(); + vi.advanceTimersByTime(50); await manager.onReady(); - await advanceTimersByTime(50); expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); expect(mockSet.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); diff --git a/tests/reactNativeEventsStore.spec.ts b/tests/reactNativeEventsStore.spec.ts index cba3185ef..0c211309f 100644 --- a/tests/reactNativeEventsStore.spec.ts +++ b/tests/reactNativeEventsStore.spec.ts @@ -13,41 +13,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, it, vi, expect } from 'vitest'; -const mockMap = new Map(); -const mockGet = jest.fn().mockImplementation((key) => { - return Promise.resolve(mockMap.get(key)); -}); - -const mockSet = jest.fn().mockImplementation((key, value) => { - mockMap.set(key, value); - return Promise.resolve(); -}); +const { mockMap, mockGet, mockSet, mockRemove, mockContains } = vi.hoisted(() => { + const mockMap = new Map(); -const mockRemove = jest.fn().mockImplementation((key) => { - if (mockMap.has(key)) { - mockMap.delete(key); - return Promise.resolve(true); - } - return Promise.resolve(false); -}); - -const mockContains = jest.fn().mockImplementation((key) => { - return Promise.resolve(mockMap.has(key)); -}); + const mockGet = vi.fn().mockImplementation((key) => { + return Promise.resolve(mockMap.get(key)); + }); + const mockSet = vi.fn().mockImplementation((key, value) => { + mockMap.set(key, value); + return Promise.resolve(); + }); -jest.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { - return jest.fn().mockImplementation(() => { - return { - get: mockGet, - contains: mockContains, - set: mockSet, - remove: mockRemove, + const mockRemove = vi.fn().mockImplementation((key) => { + if (mockMap.has(key)) { + mockMap.delete(key); + return Promise.resolve(true); } + return Promise.resolve(false); + }); + + const mockContains = vi.fn().mockImplementation((key) => { + return Promise.resolve(mockMap.has(key)); }); + + return { mockMap, mockGet, mockSet, mockRemove, mockContains }; +}); + +vi.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { + const MockReactNativeAsyncStorageCache = vi.fn(); + MockReactNativeAsyncStorageCache.prototype.get = mockGet; + MockReactNativeAsyncStorageCache.prototype.set = mockSet; + MockReactNativeAsyncStorageCache.prototype.contains = mockContains; + MockReactNativeAsyncStorageCache.prototype.remove = mockRemove; + return { 'default': MockReactNativeAsyncStorageCache }; }); import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; @@ -58,7 +60,7 @@ import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKe const STORE_KEY = 'test-store' describe('ReactNativeEventsStore', () => { - const MockedReactNativeAsyncStorageCache = jest.mocked(ReactNativeAsyncStorageCache); + const MockedReactNativeAsyncStorageCache = vi.mocked(ReactNativeAsyncStorageCache); let store: ReactNativeEventsStore<any> beforeEach(() => { @@ -83,11 +85,11 @@ describe('ReactNativeEventsStore', () => { it('uses the user provided cache', () => { const cache = { - get: jest.fn(), - contains: jest.fn(), - set: jest.fn(), - remove: jest.fn(), - } as jest.Mocked<PersistentKeyValueCache> + get: vi.fn(), + contains: vi.fn(), + set: vi.fn(), + remove: vi.fn(), + }; const store = new ReactNativeEventsStore(5, STORE_KEY, cache); store.clear(); diff --git a/tests/reactNativeHttpPollingDatafileManager.spec.ts b/tests/reactNativeHttpPollingDatafileManager.spec.ts index d9da58604..466efdb43 100644 --- a/tests/reactNativeHttpPollingDatafileManager.spec.ts +++ b/tests/reactNativeHttpPollingDatafileManager.spec.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, afterEach, it, vi, expect } from 'vitest'; -jest.mock('../lib/modules/datafile-manager/index.react_native', () => { +vi.mock('../lib/modules/datafile-manager/index.react_native', () => { return { - HttpPollingDatafileManager: jest.fn().mockImplementation(() => { + HttpPollingDatafileManager: vi.fn().mockImplementation(() => { return { get(): string { return '{}'; @@ -38,15 +39,15 @@ import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKe import { PersistentCacheProvider } from '../lib/shared_types'; describe('createHttpPollingDatafileManager', () => { - const MockedHttpPollingDatafileManager = jest.mocked(HttpPollingDatafileManager); + const MockedHttpPollingDatafileManager = vi.mocked(HttpPollingDatafileManager); beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); + vi.restoreAllMocks(); + vi.clearAllTimers(); MockedHttpPollingDatafileManager.mockClear(); }); @@ -66,9 +67,9 @@ describe('createHttpPollingDatafileManager', () => { } } - const fakePersistentCacheProvider = jest.fn().mockImplementation(() => { + const fakePersistentCacheProvider = vi.fn().mockImplementation(() => { return fakePersistentCache; - }) as jest.Mocked<PersistentCacheProvider>; + }); const noop = () => {}; diff --git a/tests/reactNativeV1EventProcessor.spec.ts b/tests/reactNativeV1EventProcessor.spec.ts index b296e6b0d..a7bf8a8f5 100644 --- a/tests/reactNativeV1EventProcessor.spec.ts +++ b/tests/reactNativeV1EventProcessor.spec.ts @@ -13,8 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, beforeEach, it, vi, expect } from 'vitest'; -jest.mock('../lib/modules/event_processor/reactNativeEventsStore'); +vi.mock('@react-native-community/netinfo'); + +vi.mock('../lib/modules/event_processor/reactNativeEventsStore'); import { ReactNativeEventsStore } from '../lib/modules/event_processor/reactNativeEventsStore'; import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; @@ -22,7 +25,7 @@ import { LogTierV1EventProcessor } from '../lib/modules/event_processor/index.re import { PersistentCacheProvider } from '../lib/shared_types'; describe('LogTierV1EventProcessor', () => { - const MockedReactNativeEventsStore = jest.mocked(ReactNativeEventsStore); + const MockedReactNativeEventsStore = vi.mocked(ReactNativeEventsStore); beforeEach(() => { MockedReactNativeEventsStore.mockClear(); @@ -48,9 +51,9 @@ describe('LogTierV1EventProcessor', () => { let call = 0; const fakeCaches = [getFakePersistentCache(), getFakePersistentCache()]; - const fakePersistentCacheProvider = jest.fn().mockImplementation(() => { + const fakePersistentCacheProvider = vi.fn().mockImplementation(() => { return fakeCaches[call++]; - }) as jest.Mocked<PersistentCacheProvider>; + }); const noop = () => {}; diff --git a/tests/requestTracker.spec.ts b/tests/requestTracker.spec.ts index 70f241500..37245835c 100644 --- a/tests/requestTracker.spec.ts +++ b/tests/requestTracker.spec.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, it, expect } from 'vitest'; import RequestTracker from '../lib/modules/event_processor/requestTracker' diff --git a/tests/sendBeaconDispatcher.spec.ts b/tests/sendBeaconDispatcher.spec.ts index 743d8dae6..2b67268d3 100644 --- a/tests/sendBeaconDispatcher.spec.ts +++ b/tests/sendBeaconDispatcher.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { describe, afterEach, it, expect } from 'vitest'; import sendBeaconDispatcher, { Event } from '../lib/plugins/event_dispatcher/send_beacon_dispatcher'; import { anyString, anything, capture, instance, mock, reset, when } from 'ts-mockito'; @@ -56,47 +57,51 @@ describe('dispatchEvent', function() { expect(sentParams).toEqual(JSON.stringify(eventObj.params)); }); - it('should call call callback with status 200 on sendBeacon success', (done) => { - var eventParams = { testParam: 'testParamValue' }; - var eventObj: Event = { - url: 'https://cdn.com/event', - httpVerb: 'POST', - params: eventParams, - }; - - when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(true); - const navigator = instance(mockNavigator); - global.navigator.sendBeacon = navigator.sendBeacon; - - sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { - try { - expect(res.statusCode).toEqual(200); - done(); - } catch(err) { - done(err); - } - }); - }); - - it('should call call callback with status 200 on sendBeacon failure', (done) => { - var eventParams = { testParam: 'testParamValue' }; - var eventObj: Event = { - url: 'https://cdn.com/event', - httpVerb: 'POST', - params: eventParams, - }; - - when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(false); - const navigator = instance(mockNavigator); - global.navigator.sendBeacon = navigator.sendBeacon; - - sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { - try { - expect(res.statusCode).toEqual(500); - done(); - } catch(err) { - done(err); - } - }); - }); + it('should call call callback with status 200 on sendBeacon success', () => + new Promise<void>((pass, fail) => { + var eventParams = { testParam: 'testParamValue' }; + var eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(true); + const navigator = instance(mockNavigator); + global.navigator.sendBeacon = navigator.sendBeacon; + + sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { + try { + expect(res.statusCode).toEqual(200); + pass(); + } catch(err) { + fail(err); + } + }); + }) + ); + + it('should call call callback with status 200 on sendBeacon failure', () => + new Promise<void>((pass, fail) => { + var eventParams = { testParam: 'testParamValue' }; + var eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(false); + const navigator = instance(mockNavigator); + global.navigator.sendBeacon = navigator.sendBeacon; + + sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { + try { + expect(res.statusCode).toEqual(500); + pass(); + } catch(err) { + fail(err); + } + }); + }) + ); }); diff --git a/tests/testUtils.ts b/tests/testUtils.ts index 2af292e09..118e3e78c 100644 --- a/tests/testUtils.ts +++ b/tests/testUtils.ts @@ -14,25 +14,24 @@ * limitations under the License. */ +import { vi } from 'vitest'; + import PersistentKeyValueCache from "../lib/plugins/key_value_cache/persistentKeyValueCache"; export function advanceTimersByTime(waitMs: number): Promise<void> { const timeoutPromise: Promise<void> = new Promise(res => setTimeout(res, waitMs)); - jest.advanceTimersByTime(waitMs); + vi.advanceTimersByTime(waitMs); return timeoutPromise; } export function getTimerCount(): number { - // Type definition for jest doesn't include this, but it exists - // https://jestjs.io/docs/en/jest-object#jestgettimercount - return (jest as any).getTimerCount(); + return vi.getTimerCount(); } - export const getTestPersistentCache = (): PersistentKeyValueCache => { const cache = { - get: jest.fn().mockImplementation((key: string): Promise<string | undefined> => { - let val = undefined; + get: vi.fn().mockImplementation((key: string): Promise<string | undefined> => { + let val : string | undefined = undefined; switch (key) { case 'opt-datafile-keyThatExists': val = JSON.stringify({ name: 'keyThatExists' }); @@ -41,18 +40,18 @@ export const getTestPersistentCache = (): PersistentKeyValueCache => { return Promise.resolve(val); }), - set: jest.fn().mockImplementation((): Promise<void> => { + set: vi.fn().mockImplementation((): Promise<void> => { return Promise.resolve(); }), - contains: jest.fn().mockImplementation((): Promise<boolean> => { + contains: vi.fn().mockImplementation((): Promise<boolean> => { return Promise.resolve(false); }), - remove: jest.fn().mockImplementation((): Promise<boolean> => { + remove: vi.fn().mockImplementation((): Promise<boolean> => { return Promise.resolve(false); }), - } as jest.Mocked<PersistentKeyValueCache>; + }; return cache; } diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts index a54188673..aa529e241 100644 --- a/tests/utils.spec.ts +++ b/tests/utils.spec.ts @@ -1,4 +1,5 @@ -/// <reference types="jest" /> +import { describe, it, expect } from 'vitest'; + import { isValidEnum, groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '../lib/utils/fns' describe('utils', () => { diff --git a/tests/v1EventProcessor.react_native.spec.ts b/tests/v1EventProcessor.react_native.spec.ts index 0a989dfd0..7722ef30c 100644 --- a/tests/v1EventProcessor.react_native.spec.ts +++ b/tests/v1EventProcessor.react_native.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, vi, expect, Mock } from 'vitest'; + +vi.mock('@react-native-community/netinfo'); +vi.mock('@react-native-async-storage/async-storage'); + import { NotificationSender } from '../lib/core/notification_center' import { NOTIFICATION_TYPES } from '../lib/utils/enums' @@ -111,10 +115,10 @@ function createConversionEvent() { describe('LogTierV1EventProcessorReactNative', () => { describe('New Events', () => { let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock + let dispatchStub: Mock beforeEach(() => { - dispatchStub = jest.fn() + dispatchStub = vi.fn() stubDispatcher = { dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { @@ -125,7 +129,7 @@ describe('LogTierV1EventProcessorReactNative', () => { }) afterEach(() => { - jest.resetAllMocks() + vi.resetAllMocks() AsyncStorage.clearStore() }) @@ -222,7 +226,7 @@ describe('LogTierV1EventProcessorReactNative', () => { it('should stop accepting events after stop is called', async () => { const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { + dispatchEvent: vi.fn((event: EventV1Request, callback: EventDispatcherCallback) => { setTimeout(() => callback({ statusCode: 204 }), 0) }) } @@ -407,11 +411,11 @@ describe('LogTierV1EventProcessorReactNative', () => { describe('when a notification center is provided', () => { it('should trigger a notification when the event dispatcher dispatches an event', async () => { const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() + dispatchEvent: vi.fn() } const notificationCenter: NotificationSender = { - sendNotifications: jest.fn() + sendNotifications: vi.fn() } const processor = new LogTierV1EventProcessor({ @@ -426,7 +430,7 @@ describe('LogTierV1EventProcessorReactNative', () => { await new Promise(resolve => setTimeout(resolve, 150)) expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] + const event = (dispatcher.dispatchEvent as Mock).mock.calls[0][0] expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) }) }) @@ -465,14 +469,14 @@ describe('LogTierV1EventProcessorReactNative', () => { describe('Pending Events', () => { let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock + let dispatchStub: Mock beforeEach(() => { - dispatchStub = jest.fn() + dispatchStub = vi.fn() }) afterEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() AsyncStorage.clearStore() }) @@ -515,7 +519,7 @@ describe('LogTierV1EventProcessorReactNative', () => { await processor.stop() - jest.clearAllMocks() + vi.clearAllMocks() receivedEvents = [] stubDispatcher = { @@ -631,7 +635,7 @@ describe('LogTierV1EventProcessorReactNative', () => { ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - jest.clearAllMocks() + vi.clearAllMocks() const visitorIds: string[] = [] stubDispatcher = { @@ -701,7 +705,7 @@ describe('LogTierV1EventProcessorReactNative', () => { // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped expect(dispatchStub).toBeCalledTimes(4) - jest.resetAllMocks() + vi.resetAllMocks() let event5 = createConversionEvent() event5.user.id = event5.uuid = 'user5' @@ -810,7 +814,7 @@ describe('LogTierV1EventProcessorReactNative', () => { // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped expect(dispatchStub).toBeCalledTimes(4) - jest.resetAllMocks() + vi.resetAllMocks() triggerInternetState(true) await new Promise(resolve => setTimeout(resolve, 50)) @@ -862,7 +866,7 @@ describe('LogTierV1EventProcessorReactNative', () => { // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped expect(dispatchStub).toBeCalledTimes(4) - jest.resetAllMocks() + vi.resetAllMocks() triggerInternetState(true) triggerInternetState(false) diff --git a/tests/v1EventProcessor.spec.ts b/tests/v1EventProcessor.spec.ts index 18cc93463..0649bad72 100644 --- a/tests/v1EventProcessor.spec.ts +++ b/tests/v1EventProcessor.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, vi, expect, Mock } from 'vitest'; import { LogTierV1EventProcessor } from '../lib/modules/event_processor/v1/v1EventProcessor' import { @@ -107,15 +107,15 @@ function createConversionEvent() { describe('LogTierV1EventProcessor', () => { let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock + let dispatchStub: Mock // TODO change this to ProjectConfig when js-sdk-models is available let testProjectConfig: any beforeEach(() => { - jest.useFakeTimers() + vi.useFakeTimers() testProjectConfig = {} - dispatchStub = jest.fn() + dispatchStub = vi.fn() stubDispatcher = { dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { @@ -126,7 +126,7 @@ describe('LogTierV1EventProcessor', () => { }) afterEach(() => { - jest.resetAllMocks() + vi.resetAllMocks() }) describe('stop()', () => { @@ -140,96 +140,104 @@ describe('LogTierV1EventProcessor', () => { } }) - it('should return a resolved promise when there is nothing in queue', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - processor.stop().then(() => { - done() - }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() + it('should return a resolved promise when there is nothing in queue', () => + new Promise<void>((done) => { + const processor = new LogTierV1EventProcessor({ + dispatcher: stubDispatcher, + flushInterval: 100, + batchSize: 100, + }) + + processor.stop().then(() => { + done() + }) }) + ) + + it('should return a promise that is resolved when the dispatcher callback returns a 200 response', () => + new Promise<void>((done) => { + const processor = new LogTierV1EventProcessor({ + dispatcher: stubDispatcher, + flushInterval: 100, + batchSize: 100, + }) + processor.start() - localCallback({ statusCode: 200 }) - }) + const impressionEvent = createImpressionEvent() + processor.process(impressionEvent) - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', done => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) + processor.stop().then(() => { + done() + }) - processor.stop().then(() => { - done() + localCallback({ statusCode: 200 }) }) + ) + + it('should return a promise that is resolved when the dispatcher callback returns a 400 response', () => + new Promise<void>((done) => { + // This test is saying that even if the request fails to send but + // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved + let localCallback: any + stubDispatcher = { + dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchStub(event) + localCallback = callback + }, + } + + const processor = new LogTierV1EventProcessor({ + dispatcher: stubDispatcher, + flushInterval: 100, + batchSize: 100, + }) + processor.start() - localCallback({ - statusCode: 400, - }) - }) + const impressionEvent = createImpressionEvent() + processor.process(impressionEvent) - it('should return a promise when multiple event batches are sent', done => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } + processor.stop().then(() => { + done() + }) - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, + localCallback({ + statusCode: 400, + }) }) - processor.start() + ) + + it('should return a promise when multiple event batches are sent', () => + new Promise<void>((done) => { + stubDispatcher = { + dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchStub(event) + callback({ statusCode: 200 }) + }, + } + + const processor = new LogTierV1EventProcessor({ + dispatcher: stubDispatcher, + flushInterval: 100, + batchSize: 100, + }) + processor.start() - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) + const impressionEvent1 = createImpressionEvent() + const impressionEvent2 = createImpressionEvent() + impressionEvent2.context.revision = '2' + processor.process(impressionEvent1) + processor.process(impressionEvent2) - processor.stop().then(() => { - expect(dispatchStub).toBeCalledTimes(2) - done() + processor.stop().then(() => { + expect(dispatchStub).toBeCalledTimes(2) + done() + }) }) - }) + ) it('should stop accepting events after stop is called', () => { const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { + dispatchEvent: vi.fn((event: EventV1Request, callback: EventDispatcherCallback) => { setTimeout(() => callback({ statusCode: 204 }), 0) }) } @@ -265,7 +273,7 @@ describe('LogTierV1EventProcessor', () => { it('should resolve the stop promise after all dispatcher requests are done', async () => { const dispatchCbs: Array<EventDispatcherCallback> = [] const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { + dispatchEvent: vi.fn((event: EventV1Request, callback: EventDispatcherCallback) => { dispatchCbs.push(callback) }) } @@ -289,7 +297,7 @@ describe('LogTierV1EventProcessor', () => { expect(stopPromiseResolved).toBe(false) dispatchCbs[0]({ statusCode: 204 }) - jest.advanceTimersByTime(100) + vi.advanceTimersByTime(100) expect(stopPromiseResolved).toBe(false) dispatchCbs[1]({ statusCode: 204 }) await stopPromise @@ -298,11 +306,11 @@ describe('LogTierV1EventProcessor', () => { it('should use the provided closingDispatcher to dispatch events on stop', async () => { const dispatcher = { - dispatchEvent: jest.fn(), + dispatchEvent: vi.fn(), } const closingDispatcher = { - dispatchEvent: jest.fn(), + dispatchEvent: vi.fn(), } const processor = new LogTierV1EventProcessor({ @@ -314,7 +322,7 @@ describe('LogTierV1EventProcessor', () => { processor.start() - const events = []; + const events : any = []; for (let i = 0; i < 4; i++) { const event = createImpressionEvent(); @@ -323,7 +331,7 @@ describe('LogTierV1EventProcessor', () => { } processor.stop(); - jest.runAllTimers(); + vi.runAllTimers(); expect(dispatcher.dispatchEvent).not.toHaveBeenCalled(); expect(closingDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); @@ -474,7 +482,7 @@ describe('LogTierV1EventProcessor', () => { expect(dispatchStub).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(100) + vi.advanceTimersByTime(100) expect(dispatchStub).toHaveBeenCalledTimes(1) expect(dispatchStub).toHaveBeenCalledWith({ @@ -494,11 +502,11 @@ describe('LogTierV1EventProcessor', () => { describe('when a notification center is provided', () => { it('should trigger a notification when the event dispatcher dispatches an event', () => { const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() + dispatchEvent: vi.fn() } const notificationCenter: NotificationSender = { - sendNotifications: jest.fn() + sendNotifications: vi.fn() } const processor = new LogTierV1EventProcessor({ @@ -512,7 +520,7 @@ describe('LogTierV1EventProcessor', () => { processor.process(impressionEvent1) expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] + const event = (dispatcher.dispatchEvent as Mock).mock.calls[0][0] expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) }) }) @@ -529,7 +537,7 @@ describe('LogTierV1EventProcessor', () => { const impressionEvent1 = createImpressionEvent() processor.process(impressionEvent1) expect(dispatchStub).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(30000) + vi.advanceTimersByTime(30000) expect(dispatchStub).toHaveBeenCalledTimes(1) expect(dispatchStub).toHaveBeenCalledWith({ url: 'https://logx.optimizely.com/v1/events', diff --git a/tests/vuidManager.spec.ts b/tests/vuidManager.spec.ts index 87ee8f666..2f412fe02 100644 --- a/tests/vuidManager.spec.ts +++ b/tests/vuidManager.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; import { VuidManager } from '../lib/plugins/vuid_manager'; import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 877e2b462..d27f5db0d 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -2,13 +2,14 @@ "extends": "./tsconfig.json", "compilerOptions": { "types": [ - "jest" + "vitest/jsdom" ], "typeRoots": [ "./node_modules/@types" ] }, "include": [ + "tests/**/*.ts", "**/*.spec.ts" ] } diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 000000000..d74a1e1fd --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + onConsoleLog: () => true, + environment: 'happy-dom', + include: ['**/*.spec.ts'], + typecheck: { + tsconfig: 'tsconfig.spec.json', + }, + }, +}); From e7cc602fb9526d78ec29fa298584bb01a3391645 Mon Sep 17 00:00:00 2001 From: Farhan Anjum <Farhan.Anjum@optimizely.com> Date: Wed, 25 Sep 2024 21:50:01 +0600 Subject: [PATCH 090/200] [FSSDK-10665] fix: Github Actions YAML files vulnerable to script injections corrected (#946) * [FSSDK-10665] fix: Github Actions YAML files vulnerable to script injections corrected * Update release.yml unnecessary assignment of default environment variable * Update release.yml renamed ALTERNATE_RELEASE_TAG to GITHUB_RELEASE_TAG --- .github/workflows/integration_test.yml | 12 ++++++++---- .github/workflows/release.yml | 9 +++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 6b73ba748..70b391e18 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -23,15 +23,19 @@ jobs: path: 'home/runner/travisci-tools' ref: 'master' - name: set SDK Branch if PR + env: + HEAD_REF: ${{ github.head_ref }} if: ${{ github.event_name == 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$HEAD_REF" >> $GITHUB_ENV + echo "TRAVIS_BRANCH=$HEAD_REF" >> $GITHUB_ENV - name: set SDK Branch if not pull request + env: + REF_NAME: ${{ github.ref_name }} if: ${{ github.event_name != 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$REF_NAME" >> $GITHUB_ENV + echo "TRAVIS_BRANCH=$REF_NAME" >> $GITHUB_ENV - name: Trigger build env: SDK: javascript diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d0e71680..ba839b17d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,20 +32,21 @@ jobs: echo "latest-release-tag=$(curl -qsSL \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ - "${{ github.api_url }}/repos/${{ github.repository }}/releases/latest" \ + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/latest" \ | jq -r .tag_name)" >> $GITHUB_OUTPUT - id: npm-tag name: Determine NPM tag + env: + GITHUB_RELEASE_TAG: ${{ github.event.release.tag_name }} run: | VERSION=$(jq -r '.version' package.json) LATEST_RELEASE_TAG="${{ steps.latest-release.outputs['latest-release-tag']}}" - + if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then - GITHUB_REF=${{ github.ref }} RELEASE_TAG=${GITHUB_REF#refs/tags/} else - RELEASE_TAG="${{ github.event.release.tag_name }}" + RELEASE_TAG=$GITHUB_RELEASE_TAG fi if [[ $RELEASE_TAG == $LATEST_RELEASE_TAG ]]; then From 1d814a2fa1d963342fe9dbe65c275bbb3e8e538f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 27 Sep 2024 21:58:03 +0600 Subject: [PATCH 091/200] [FSSDK-10619] Refactor project config manager to be injectable (#945) --- lib/common_exports.ts | 4 +- lib/core/bucketer/index.tests.js | 4 +- lib/core/decision_service/index.tests.js | 38 +- lib/core/decision_service/index.ts | 32 +- lib/core/event_builder/event_helpers.tests.js | 4 +- lib/core/event_builder/event_helpers.ts | 4 +- lib/core/event_builder/index.tests.js | 4 +- lib/core/event_builder/index.ts | 4 +- .../notification_registry.tests.ts | 62 -- .../notification_registry.ts | 65 -- lib/core/odp/odp_event_api_manager.ts | 4 +- lib/core/odp/odp_event_manager.ts | 4 +- lib/core/optimizely_config/index.tests.js | 4 +- lib/core/optimizely_config/index.ts | 4 +- .../project_config_manager.tests.js | 490 --------- .../project_config/project_config_manager.ts | 276 ----- lib/index.browser.tests.js | 223 ++-- lib/index.browser.ts | 8 +- lib/index.lite.tests.js | 9 +- lib/index.lite.ts | 4 +- lib/index.node.tests.js | 38 +- lib/index.node.ts | 37 +- lib/index.react_native.ts | 13 +- .../datafile-manager/backoffController.ts | 46 - .../browserDatafileManager.ts | 32 - .../datafile-manager/browserRequest.ts | 96 -- lib/modules/datafile-manager/eventEmitter.ts | 64 -- lib/modules/datafile-manager/http.ts | 37 - .../httpPollingDatafileManager.ts | 348 ------- .../datafile-manager/index.react_native.ts | 18 - .../datafile-manager/nodeDatafileManager.ts | 52 - lib/modules/datafile-manager/nodeRequest.ts | 154 --- .../reactNativeDatafileManager.ts | 34 - lib/optimizely/index.tests.js | 504 +++++----- lib/optimizely/index.ts | 95 +- lib/optimizely_user_context/index.tests.js | 49 +- lib/optimizely_user_context/index.ts | 38 +- .../browser_http_polling_datafile_manager.ts | 49 - .../http_polling_datafile_manager.tests.js | 128 --- .../http_polling_datafile_manager.ts | 49 - .../no_op_datafile_manager.tests.js | 42 - .../no_op_datafile_manager.ts | 45 - ...ct_native_http_polling_datafile_manager.ts | 54 - .../odp/event_api_manager/index.browser.ts | 33 +- .../odp/event_api_manager/index.node.ts | 33 +- lib/plugins/odp_manager/index.browser.ts | 12 +- lib/plugins/odp_manager/index.node.ts | 12 +- .../config_manager_factory.browser.spec.ts | 85 ++ .../config_manager_factory.browser.ts | 27 + .../config_manager_factory.node.spec.ts | 86 ++ .../config_manager_factory.node.ts | 28 + ...onfig_manager_factory.react_native.spec.ts | 102 ++ .../config_manager_factory.react_native.ts | 29 + .../config_manager_factory.spec.ts | 112 +++ lib/project_config/config_manager_factory.ts | 76 ++ .../config.ts => project_config/constant.ts} | 0 .../datafile_manager.ts} | 40 +- .../polling_datafile_manager.spec.ts | 951 ++++++++++++++++++ .../polling_datafile_manager.ts | 245 +++++ .../project_config.tests.js} | 56 +- .../project_config.ts} | 52 +- .../project_config_manager.spec.ts | 522 ++++++++++ lib/project_config/project_config_manager.ts | 219 ++++ .../project_config/project_config_schema.ts | 2 +- lib/service.spec.ts | 107 ++ lib/service.ts | 94 ++ lib/shared_types.ts | 12 +- lib/tests/mock/mock_datafile_manager.ts | 77 ++ .../mock/mock_logger.ts} | 18 +- lib/tests/mock/mock_project_config_manager.ts | 51 + lib/tests/mock/mock_repeater.ts | 67 ++ lib/tests/mock/mock_request_handler.ts | 43 + lib/tests/{test_data.js => test_data.ts} | 8 +- lib/utils/event_emitter/event_emitter.spec.ts | 101 ++ lib/utils/event_emitter/event_emitter.ts | 53 + .../browser_request_handler.ts | 16 +- lib/utils/http_request_handler/http.ts | 9 +- .../node_request_handler.ts | 13 +- .../json_schema_validator/index.tests.js | 4 +- lib/utils/json_schema_validator/index.ts | 4 +- lib/utils/microtask/index.spec.ts | 43 + lib/utils/microtask/index.tests.js | 38 - lib/utils/microtask/index.ts | 6 +- lib/utils/repeater/repeater.spec.ts | 284 ++++++ lib/utils/repeater/repeater.ts | 136 +++ .../index.node.ts => utils/type.ts} | 15 +- tests/backoffController.spec.ts | 65 -- tests/browserDatafileManager.spec.ts | 107 -- tests/browserRequest.spec.ts | 134 --- tests/browserRequestHandler.spec.ts | 4 +- tests/eventEmitter.spec.ts | 116 --- tests/httpPollingDatafileManager.spec.ts | 744 -------------- .../httpPollingDatafileManagerPolling.spec.ts | 62 -- tests/index.react_native.spec.ts | 51 +- tests/nodeDatafileManager.spec.ts | 187 ---- tests/nodeRequest.spec.ts | 217 ---- tests/nodeRequestHandler.spec.ts | 4 +- tests/odpManager.browser.spec.ts | 16 +- tests/reactNativeDatafileManager.spec.ts | 178 ---- ...ctNativeHttpPollingDatafileManager.spec.ts | 89 -- tsconfig.json | 1 + vitest.config.mts | 16 + 102 files changed, 4365 insertions(+), 4816 deletions(-) delete mode 100644 lib/core/notification_center/notification_registry.tests.ts delete mode 100644 lib/core/notification_center/notification_registry.ts delete mode 100644 lib/core/project_config/project_config_manager.tests.js delete mode 100644 lib/core/project_config/project_config_manager.ts delete mode 100644 lib/modules/datafile-manager/backoffController.ts delete mode 100644 lib/modules/datafile-manager/browserDatafileManager.ts delete mode 100644 lib/modules/datafile-manager/browserRequest.ts delete mode 100644 lib/modules/datafile-manager/eventEmitter.ts delete mode 100644 lib/modules/datafile-manager/http.ts delete mode 100644 lib/modules/datafile-manager/httpPollingDatafileManager.ts delete mode 100644 lib/modules/datafile-manager/index.react_native.ts delete mode 100644 lib/modules/datafile-manager/nodeDatafileManager.ts delete mode 100644 lib/modules/datafile-manager/nodeRequest.ts delete mode 100644 lib/modules/datafile-manager/reactNativeDatafileManager.ts delete mode 100644 lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts delete mode 100644 lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js delete mode 100644 lib/plugins/datafile_manager/http_polling_datafile_manager.ts delete mode 100644 lib/plugins/datafile_manager/no_op_datafile_manager.tests.js delete mode 100644 lib/plugins/datafile_manager/no_op_datafile_manager.ts delete mode 100644 lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts create mode 100644 lib/project_config/config_manager_factory.browser.spec.ts create mode 100644 lib/project_config/config_manager_factory.browser.ts create mode 100644 lib/project_config/config_manager_factory.node.spec.ts create mode 100644 lib/project_config/config_manager_factory.node.ts create mode 100644 lib/project_config/config_manager_factory.react_native.spec.ts create mode 100644 lib/project_config/config_manager_factory.react_native.ts create mode 100644 lib/project_config/config_manager_factory.spec.ts create mode 100644 lib/project_config/config_manager_factory.ts rename lib/{modules/datafile-manager/config.ts => project_config/constant.ts} (100%) rename lib/{modules/datafile-manager/datafileManager.ts => project_config/datafile_manager.ts} (55%) create mode 100644 lib/project_config/polling_datafile_manager.spec.ts create mode 100644 lib/project_config/polling_datafile_manager.ts rename lib/{core/project_config/index.tests.js => project_config/project_config.tests.js} (96%) rename lib/{core/project_config/index.ts => project_config/project_config.ts} (96%) create mode 100644 lib/project_config/project_config_manager.spec.ts create mode 100644 lib/project_config/project_config_manager.ts rename lib/{core => }/project_config/project_config_schema.ts (99%) create mode 100644 lib/service.spec.ts create mode 100644 lib/service.ts create mode 100644 lib/tests/mock/mock_datafile_manager.ts rename lib/{modules/datafile-manager/index.browser.ts => tests/mock/mock_logger.ts} (61%) create mode 100644 lib/tests/mock/mock_project_config_manager.ts create mode 100644 lib/tests/mock/mock_repeater.ts create mode 100644 lib/tests/mock/mock_request_handler.ts rename lib/tests/{test_data.js => test_data.ts} (99%) create mode 100644 lib/utils/event_emitter/event_emitter.spec.ts create mode 100644 lib/utils/event_emitter/event_emitter.ts create mode 100644 lib/utils/microtask/index.spec.ts delete mode 100644 lib/utils/microtask/index.tests.js create mode 100644 lib/utils/repeater/repeater.spec.ts create mode 100644 lib/utils/repeater/repeater.ts rename lib/{modules/datafile-manager/index.node.ts => utils/type.ts} (61%) delete mode 100644 tests/backoffController.spec.ts delete mode 100644 tests/browserDatafileManager.spec.ts delete mode 100644 tests/browserRequest.spec.ts delete mode 100644 tests/eventEmitter.spec.ts delete mode 100644 tests/httpPollingDatafileManager.spec.ts delete mode 100644 tests/httpPollingDatafileManagerPolling.spec.ts delete mode 100644 tests/nodeDatafileManager.spec.ts delete mode 100644 tests/nodeRequest.spec.ts delete mode 100644 tests/reactNativeDatafileManager.spec.ts delete mode 100644 tests/reactNativeHttpPollingDatafileManager.spec.ts diff --git a/lib/common_exports.ts b/lib/common_exports.ts index 6c6374a70..c2718e911 100644 --- a/lib/common_exports.ts +++ b/lib/common_exports.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023 Optimizely + * Copyright 2023-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,3 +17,5 @@ export { LogLevel, LogHandler, getLogger, setLogHandler } from './modules/logging'; export { LOG_LEVEL } from './utils/enums'; export { createLogger } from './plugins/logger'; +export { createStaticProjectConfigManager } from './project_config/config_manager_factory'; +export { PollingConfigManagerConfig } from './project_config/config_manager_factory'; diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index 97d261c04..e30c9129e 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2019-2022, Optimizely + * Copyright 2016-2017, 2019-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import { LOG_LEVEL, } from '../../utils/enums'; import { createLogger } from '../../plugins/logger'; -import projectConfig from '../project_config'; +import projectConfig from '../../project_config/project_config'; import { getTestProjectConfig } from '../../tests/test_data'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index ba0dd5fbb..b9197ebab 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2017-2022 Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2017-2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import sinon from 'sinon'; import { assert } from 'chai'; import cloneDeep from 'lodash/cloneDeep'; @@ -29,11 +29,13 @@ import { createForwardingEventProcessor } from '../../plugins/event_processor/fo import { createNotificationCenter } from '../notification_center'; import Optimizely from '../../optimizely'; import OptimizelyUserContext from '../../optimizely_user_context'; -import projectConfig from '../project_config'; +import projectConfig, { createProjectConfig } from '../../project_config/project_config'; import AudienceEvaluator from '../audience_evaluator'; import errorHandler from '../../plugins/error_handler'; import eventDispatcher from '../../plugins/event_dispatcher/index.node'; import * as jsonSchemaValidator from '../../utils/json_schema_validator'; +import { getMockProjectConfigManager } from '../../tests/mock/mock_project_config_manager'; + import { getTestProjectConfig, getTestProjectConfigWithFeatures, @@ -1067,7 +1069,9 @@ describe('lib/core/decision_service', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: cloneDeep(testData), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(cloneDeep(testData)) + }), jsonSchemaValidator: jsonSchemaValidator, isValidInstance: true, logger: createdLogger, diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 28f97a09e..c3fea53eb 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2017-2022 Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2017-2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { LogHandler } from '../../modules/logging'; import { sprintf } from '../../utils/fns'; @@ -38,7 +38,7 @@ import { getVariationKeyFromId, isActive, ProjectConfig, -} from '../project_config'; +} from '../../project_config/project_config'; import { AudienceEvaluator, createAudienceEvaluator } from '../audience_evaluator'; import * as stringValidator from '../../utils/string_value_validator'; import { diff --git a/lib/core/event_builder/event_helpers.tests.js b/lib/core/event_builder/event_helpers.tests.js index 3d722b975..552a72e24 100644 --- a/lib/core/event_builder/event_helpers.tests.js +++ b/lib/core/event_builder/event_helpers.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, Optimizely + * Copyright 2019-2020, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import sinon from 'sinon'; import { assert } from 'chai'; import fns from '../../utils/fns'; -import * as projectConfig from '../project_config'; +import * as projectConfig from '../../project_config/project_config'; import * as decision from '../decision'; import { buildImpressionEvent, buildConversionEvent } from './event_helpers'; diff --git a/lib/core/event_builder/event_helpers.ts b/lib/core/event_builder/event_helpers.ts index 071a1427a..9c0fc8257 100644 --- a/lib/core/event_builder/event_helpers.ts +++ b/lib/core/event_builder/event_helpers.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2022, Optimizely + * Copyright 2019-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import { getEventId, getLayerId, ProjectConfig, -} from '../project_config'; +} from '../../project_config/project_config'; const logger = getLogger('EVENT_BUILDER'); diff --git a/lib/core/event_builder/index.tests.js b/lib/core/event_builder/index.tests.js index 39ed140ad..4fd6053a9 100644 --- a/lib/core/event_builder/index.tests.js +++ b/lib/core/event_builder/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2021, Optimizely + * Copyright 2016-2021, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { assert } from 'chai'; import fns from '../../utils/fns'; import testData from '../../tests/test_data'; -import projectConfig from '../project_config'; +import projectConfig from '../../project_config/project_config'; import packageJSON from '../../../package.json'; import { getConversionEvent, getImpressionEvent } from './'; diff --git a/lib/core/event_builder/index.ts b/lib/core/event_builder/index.ts index cd6781529..f896adbea 100644 --- a/lib/core/event_builder/index.ts +++ b/lib/core/event_builder/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2022, Optimizely + * Copyright 2016-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import { getLayerId, getVariationKeyFromId, ProjectConfig, -} from '../project_config'; +} from '../../project_config/project_config'; import * as eventTagUtils from '../../utils/event_tag_utils'; import { isAttributeValid } from '../../utils/attributes_validator'; import { EventTags, UserAttributes, Event as EventLoggingEndpoint } from '../../shared_types'; diff --git a/lib/core/notification_center/notification_registry.tests.ts b/lib/core/notification_center/notification_registry.tests.ts deleted file mode 100644 index 3a99b052c..000000000 --- a/lib/core/notification_center/notification_registry.tests.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, it } from 'mocha'; -import { expect } from 'chai'; - -import { NotificationRegistry } from './notification_registry'; - -describe('Notification Registry', () => { - it('Returns null notification center when SDK Key is null', () => { - const notificationCenter = NotificationRegistry.getNotificationCenter(); - expect(notificationCenter).to.be.undefined; - }); - - it('Returns the same notification center when SDK Keys are the same and not null', () => { - const sdkKey = 'testSDKKey'; - const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKey); - const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKey); - expect(notificationCenterA).to.eql(notificationCenterB); - }); - - it('Returns different notification centers when SDK Keys are not the same', () => { - const sdkKeyA = 'testSDKKeyA'; - const sdkKeyB = 'testSDKKeyB'; - const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKeyA); - const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKeyB); - expect(notificationCenterA).to.not.eql(notificationCenterB); - }); - - it('Removes old notification centers from the registry when removeNotificationCenter is called on the registry', () => { - const sdkKey = 'testSDKKey'; - const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKey); - NotificationRegistry.removeNotificationCenter(sdkKey); - - const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKey); - - expect(notificationCenterA).to.not.eql(notificationCenterB); - }); - - it('Does not throw an error when calling removeNotificationCenter with a null SDK Key', () => { - const sdkKey = 'testSDKKey'; - const notificationCenterA = NotificationRegistry.getNotificationCenter(sdkKey); - NotificationRegistry.removeNotificationCenter(); - - const notificationCenterB = NotificationRegistry.getNotificationCenter(sdkKey); - - expect(notificationCenterA).to.eql(notificationCenterB); - }); -}); diff --git a/lib/core/notification_center/notification_registry.ts b/lib/core/notification_center/notification_registry.ts deleted file mode 100644 index 12fe1178e..000000000 --- a/lib/core/notification_center/notification_registry.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; -import { NotificationCenter, createNotificationCenter } from '../../core/notification_center'; - -/** - * Internal notification center registry for managing multiple notification centers. - */ -export class NotificationRegistry { - private static _notificationCenters = new Map<string, NotificationCenter>(); - - constructor() {} - - /** - * Retrieves an SDK Key's corresponding notification center in the registry if it exists, otherwise it creates one - * @param sdkKey SDK Key to be used for the notification center tied to the ODP Manager - * @param logger Logger to be used for the corresponding notification center - * @returns {NotificationCenter | undefined} a notification center instance for ODP Manager if a valid SDK Key is provided, otherwise undefined - */ - static getNotificationCenter(sdkKey?: string, logger: LogHandler = getLogger()): NotificationCenter | undefined { - if (!sdkKey) { - logger.log(LogLevel.ERROR, 'No SDK key provided to getNotificationCenter.'); - return undefined; - } - - let notificationCenter; - if (this._notificationCenters.has(sdkKey)) { - notificationCenter = this._notificationCenters.get(sdkKey); - } else { - notificationCenter = createNotificationCenter({ - logger, - errorHandler: { handleError: () => {} }, - }); - this._notificationCenters.set(sdkKey, notificationCenter); - } - - return notificationCenter; - } - - static removeNotificationCenter(sdkKey?: string): void { - if (!sdkKey) { - return; - } - - const notificationCenter = this._notificationCenters.get(sdkKey); - if (notificationCenter) { - notificationCenter.clearAllNotificationListeners(); - this._notificationCenters.delete(sdkKey); - } - } -} diff --git a/lib/core/odp/odp_event_api_manager.ts b/lib/core/odp/odp_event_api_manager.ts index 35ffcc4e8..6b3362f8c 100644 --- a/lib/core/odp/odp_event_api_manager.ts +++ b/lib/core/odp/odp_event_api_manager.ts @@ -16,7 +16,7 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { OdpEvent } from './odp_event'; -import { RequestHandler } from '../../utils/http_request_handler/http'; +import { HttpMethod, RequestHandler } from '../../utils/http_request_handler/http'; import { OdpConfig } from './odp_config'; import { ERROR_MESSAGES } from '../../utils/enums'; @@ -109,7 +109,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { odpConfig: OdpConfig, events: OdpEvent[] ): { - method: string; + method: HttpMethod; endpoint: string; headers: { [key: string]: string }; data: string; diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 3b91d7712..2ffbbeaa3 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -24,7 +24,7 @@ import { OdpConfig } from './odp_config'; import { IOdpEventApiManager } from './odp_event_api_manager'; import { invalidOdpDataFound } from './odp_utils'; import { IUserAgentParser } from './user_agent_parser'; -import { scheduleMicrotaskOrTimeout } from '../../utils/microtask'; +import { scheduleMicrotask } from '../../utils/microtask'; const MAX_RETRIES = 3; @@ -394,7 +394,7 @@ export abstract class OdpEventManager implements IOdpEventManager { if (batch.length > 0) { // put sending the event on another event loop - scheduleMicrotaskOrTimeout(async () => { + scheduleMicrotask(async () => { let shouldRetry: boolean; let attemptNumber = 0; do { diff --git a/lib/core/optimizely_config/index.tests.js b/lib/core/optimizely_config/index.tests.js index 9f147c1b5..d4100e0da 100644 --- a/lib/core/optimizely_config/index.tests.js +++ b/lib/core/optimizely_config/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2019-2021, Optimizely + * Copyright 2019-2021, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { cloneDeep } from 'lodash'; import sinon from 'sinon'; import { createOptimizelyConfig, OptimizelyConfig } from './'; -import { createProjectConfig } from '../project_config'; +import { createProjectConfig } from '../../project_config/project_config'; import { getTestProjectConfigWithFeatures, getTypedAudiencesConfig, diff --git a/lib/core/optimizely_config/index.ts b/lib/core/optimizely_config/index.ts index 4b435b830..d8987b6c7 100644 --- a/lib/core/optimizely_config/index.ts +++ b/lib/core/optimizely_config/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2023, Optimizely + * Copyright 2020-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ import { LoggerFacade, getLogger } from '../../modules/logging'; -import { ProjectConfig } from '../project_config'; +import { ProjectConfig } from '../../project_config/project_config'; import { DEFAULT_OPERATOR_TYPES } from '../condition_tree_evaluator'; import { Audience, diff --git a/lib/core/project_config/project_config_manager.tests.js b/lib/core/project_config/project_config_manager.tests.js deleted file mode 100644 index b8fe8f8d3..000000000 --- a/lib/core/project_config/project_config_manager.tests.js +++ /dev/null @@ -1,490 +0,0 @@ -/** - * Copyright 2019-2020, 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { assert } from 'chai'; -import { cloneDeep } from 'lodash'; - -import { sprintf } from '../../utils/fns'; -import * as logging from '../../modules/logging'; -import datafileManager from '../../modules/datafile-manager/index.node'; - -import * as projectConfig from './index'; -import { ERROR_MESSAGES, LOG_MESSAGES } from '../../utils/enums'; -import testData from '../../tests/test_data'; -import * as projectConfigManager from './project_config_manager'; -import * as optimizelyConfig from '../optimizely_config'; -import * as jsonSchemaValidator from '../../utils/json_schema_validator'; -import { createHttpPollingDatafileManager } from '../../plugins/datafile_manager/http_polling_datafile_manager'; - -const logger = logging.getLogger(); - -describe('lib/core/project_config/project_config_manager', function() { - var globalStubErrorHandler; - var stubLogHandler; - beforeEach(function() { - sinon.stub(datafileManager, 'HttpPollingDatafileManager').returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(null), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns({ then: function() {} }), - }); - globalStubErrorHandler = { - handleError: sinon.stub(), - }; - logging.setErrorHandler(globalStubErrorHandler); - logging.setLogLevel('notset'); - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogHandler(stubLogHandler); - }); - - afterEach(function() { - datafileManager.HttpPollingDatafileManager.restore(); - logging.resetErrorHandler(); - logging.resetLogger(); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if neither datafile nor sdkKey are passed into the constructor', function() { - var manager = projectConfigManager.createProjectConfigManager({}); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.DATAFILE_AND_SDK_KEY_MISSING, 'PROJECT_CONFIG_MANAGER')); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile JSON is malformed', function() { - var invalidDatafileJSON = 'abc'; - var manager = projectConfigManager.createProjectConfigManager({ - datafile: invalidDatafileJSON, - }); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_MALFORMED, 'CONFIG_VALIDATOR')); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile is not valid', function() { - var invalidDatafile = testData.getTestProjectConfig(); - delete invalidDatafile['projectId']; - var manager = projectConfigManager.createProjectConfigManager({ - datafile: invalidDatafile, - jsonSchemaValidator: jsonSchemaValidator, - }); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual( - errorMessage, - sprintf(ERROR_MESSAGES.INVALID_DATAFILE, 'JSON_SCHEMA_VALIDATOR (Project Config JSON Schema)', 'projectId', 'is missing and it is required'), - ); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile version is not supported', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getUnsupportedVersionConfig(), - jsonSchemaValidator: jsonSchemaValidator, - }); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_VERSION, 'CONFIG_VALIDATOR', '5')); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - describe('skipping JSON schema validation', function() { - beforeEach(function() { - sinon.spy(jsonSchemaValidator, 'validate'); - }); - - afterEach(function() { - jsonSchemaValidator.validate.restore(); - }); - - it('should skip JSON schema validation if jsonSchemaValidator is not provided', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getTestProjectConfig(), - }); - sinon.assert.notCalled(jsonSchemaValidator.validate); - return manager.onReady(); - }); - - it('should not skip JSON schema validation if jsonSchemaValidator is provided', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getTestProjectConfig(), - jsonSchemaValidator: jsonSchemaValidator, - }); - sinon.assert.calledOnce(jsonSchemaValidator.validate); - sinon.assert.calledOnce(stubLogHandler.log); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.VALID_DATAFILE, 'PROJECT_CONFIG')); - - return manager.onReady(); - }); - }); - - it('should return a valid datafile from getConfig and resolve onReady with a successful result', function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = projectConfigManager.createProjectConfigManager({ - datafile: cloneDeep(configWithFeatures), - }); - assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(configWithFeatures)); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - }); - }); - - it('calls onUpdate listeners once when constructed with a valid datafile and without sdkKey', function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = projectConfigManager.createProjectConfigManager({ - datafile: configWithFeatures, - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function() { - sinon.assert.calledOnce(onUpdateSpy); - }); - }); - - describe('with a datafile manager', function() { - it('passes the correct options to datafile manager', function() { - var config = testData.getTestProjectConfig() - let datafileOptions = { - autoUpdate: true, - updateInterval: 10000, - } - projectConfigManager.createProjectConfigManager({ - datafile: config, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, config, datafileOptions), - }); - sinon.assert.calledOnce(datafileManager.HttpPollingDatafileManager); - sinon.assert.calledWithExactly( - datafileManager.HttpPollingDatafileManager, - sinon.match({ - datafile: JSON.stringify(config), - sdkKey: '12345', - autoUpdate: true, - updateInterval: 10000, - }) - ); - }); - - describe('when constructed with sdkKey and without datafile', function() { - it('updates itself when the datafile manager is ready, fulfills its onReady promise with a successful result, and then emits updates', function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(cloneDeep(configWithFeatures))), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - assert.isNull(manager.getConfig()); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(configWithFeatures)); - - var nextDatafile = testData.getTestProjectConfigWithFeatures(); - nextDatafile.experiments.push({ - key: 'anotherTestExp', - status: 'Running', - forcedVariations: {}, - audienceIds: [], - layerId: '253442', - trafficAllocation: [{ entityId: '99977477477747747', endOfRange: 10000 }], - id: '1237847778', - variations: [{ key: 'variation', id: '99977477477747747' }], - }); - nextDatafile.revision = '36'; - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - fakeDatafileManager.get.returns(cloneDeep(nextDatafile)); - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - updateListener({ datafile: nextDatafile }); - assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(nextDatafile)); - }); - }); - - it('calls onUpdate listeners after becoming ready, and after the datafile manager emits updates', async function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - await manager.onReady(); - sinon.assert.calledOnce(onUpdateSpy); - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - - await Promise.resolve(); - sinon.assert.calledTwice(onUpdateSpy); - }); - - it('can remove onUpdate listeners using the function returned from onUpdate', async function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - await manager.onReady(); - var onUpdateSpy = sinon.spy(); - var unsubscribe = manager.onUpdate(onUpdateSpy); - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - - updateListener({ datafile: newDatafile }); - // allow queued micortasks to run - await Promise.resolve(); - - sinon.assert.calledOnce(onUpdateSpy); - unsubscribe(); - newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '37'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - // // Should not call onUpdateSpy again since we unsubscribed - updateListener({ datafile: testData.getTestProjectConfigWithFeatures() }); - sinon.assert.calledOnce(onUpdateSpy); - }); - - it('fulfills its ready promise with an unsuccessful result when the datafile manager emits an invalid datafile', function() { - var invalidDatafile = testData.getTestProjectConfig(); - delete invalidDatafile['projectId']; - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(invalidDatafile)), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - jsonSchemaValidator: jsonSchemaValidator, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('fullfils its ready promise with an unsuccessful result when the datafile manager onReady promise rejects', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(null), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.reject(new Error('Failed to become ready'))), - }); - var manager = projectConfigManager.createProjectConfigManager({ - jsonSchemaValidator: jsonSchemaValidator, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('calls stop on its datafile manager when its stop method is called', function() { - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - manager.stop(); - sinon.assert.calledOnce(datafileManager.HttpPollingDatafileManager.getCall(0).returnValue.stop); - }); - - it('does not log an error message', function() { - projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - sinon.assert.notCalled(stubLogHandler.log); - }); - }); - - describe('when constructed with sdkKey and with a valid datafile object', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners if datafile does not change', async function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - - const handlers = []; - const mockDatafileManager = { - start: () => {}, - get: () => JSON.stringify(configWithFeatures), - on: (event, fn) => handlers.push(fn), - onReady: () => Promise.resolve(), - pushUpdate: (datafile) => handlers.forEach(handler => handler({ datafile })), - }; - - var manager = projectConfigManager.createProjectConfigManager({ - datafile: configWithFeatures, - sdkKey: '12345', - datafileManager: mockDatafileManager, - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - - const result = await manager.onReady(); - assert.include(result, { - success: true, - }); - - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - // allow queued microtasks to run - await Promise.resolve(); - - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - await Promise.resolve(); - - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - await Promise.resolve(); - - - configWithFeatures.revision = '99'; - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - await Promise.resolve(); - - sinon.assert.callCount(onUpdateSpy, 2); - }); - }); - - describe('when constructed with sdkKey and with a valid datafile string', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners if datafile does not change', async function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - - const handlers = []; - const mockDatafileManager = { - start: () => {}, - get: () => JSON.stringify(configWithFeatures), - on: (event, fn) => handlers.push(fn), - onReady: () => Promise.resolve(), - pushUpdate: (datafile) => handlers.forEach(handler => handler({ datafile })), - }; - - var manager = projectConfigManager.createProjectConfigManager({ - datafile: JSON.stringify(configWithFeatures), - sdkKey: '12345', - datafileManager: mockDatafileManager, - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - - const result = await manager.onReady(); - assert.include(result, { - success: true, - }); - - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - // allow queued microtasks to run - await Promise.resolve(); - - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - await Promise.resolve(); - - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - await Promise.resolve(); - - - configWithFeatures.revision = '99'; - mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); - await Promise.resolve(); - - sinon.assert.callCount(onUpdateSpy, 2); - }); - }); - - describe('test caching of optimizely config', function() { - beforeEach(function() { - sinon.stub(optimizelyConfig, 'createOptimizelyConfig'); - }); - - afterEach(function() { - optimizelyConfig.createOptimizelyConfig.restore(); - }); - - it('should return the same config until revision is changed', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getTestProjectConfig(), - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, testData.getTestProjectConfig()), - }); - // validate it should return the existing optimizely config - manager.getOptimizelyConfig(); - sinon.assert.calledOnce(optimizelyConfig.createOptimizelyConfig); - // create config with new revision - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - manager.getOptimizelyConfig(); - // verify the optimizely config is updated - sinon.assert.calledTwice(optimizelyConfig.createOptimizelyConfig); - }); - }); - }); -}); diff --git a/lib/core/project_config/project_config_manager.ts b/lib/core/project_config/project_config_manager.ts deleted file mode 100644 index b0fe25ddd..000000000 --- a/lib/core/project_config/project_config_manager.ts +++ /dev/null @@ -1,276 +0,0 @@ -/** - * Copyright 2019-2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../../modules/logging'; -import { sprintf } from '../../utils/fns'; - -import { ERROR_MESSAGES } from '../../utils/enums'; -import { createOptimizelyConfig } from '../optimizely_config'; -import { OnReadyResult, OptimizelyConfig, DatafileManager } from '../../shared_types'; -import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from '../project_config'; -import { scheduleMicrotaskOrTimeout } from '../../utils/microtask'; - -const logger = getLogger(); -const MODULE_NAME = 'PROJECT_CONFIG_MANAGER'; - -interface ProjectConfigManagerConfig { - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object; - jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean; - }; - sdkKey?: string; - datafileManager?: DatafileManager; -} - -/** - * Return an error message derived from a thrown value. If the thrown value is - * an error, return the error's message property. Otherwise, return a default - * provided by the second argument. - * @param {Error|null} maybeError - * @param {string} defaultMessage - * @return {string} - */ -function getErrorMessage(maybeError: Error | null, defaultMessage?: string): string { - if (maybeError instanceof Error) { - return maybeError.message; - } - return defaultMessage || 'Unknown error'; -} - -/** - * ProjectConfigManager provides project config objects via its methods - * getConfig and onUpdate. It uses a DatafileManager to fetch datafiles. It is - * responsible for parsing and validating datafiles, and converting datafile - * string into project config objects. - * @param {ProjectConfigManagerConfig} config - */ -export class ProjectConfigManager { - private updateListeners: Array<(config: ProjectConfig) => void> = []; - private configObj: ProjectConfig | null = null; - private optimizelyConfigObj: OptimizelyConfig | null = null; - private readyPromise: Promise<OnReadyResult>; - public jsonSchemaValidator: { validate(jsonObject: unknown): boolean } | undefined; - public datafileManager: DatafileManager | null = null; - - constructor(config: ProjectConfigManagerConfig) { - try { - this.jsonSchemaValidator = config.jsonSchemaValidator; - - if (!config.datafile && !config.sdkKey) { - const datafileAndSdkKeyMissingError = new Error( - sprintf(ERROR_MESSAGES.DATAFILE_AND_SDK_KEY_MISSING, MODULE_NAME) - ); - this.readyPromise = Promise.resolve({ - success: false, - reason: getErrorMessage(datafileAndSdkKeyMissingError), - }); - logger.error(datafileAndSdkKeyMissingError); - return; - } - - let handleNewDatafileException = null; - if (config.datafile) { - handleNewDatafileException = this.handleNewDatafile(config.datafile); - } - - if (config.sdkKey && config.datafileManager) { - this.datafileManager = config.datafileManager; - this.datafileManager.start(); - - this.readyPromise = this.datafileManager - .onReady() - .then(this.onDatafileManagerReadyFulfill.bind(this), this.onDatafileManagerReadyReject.bind(this)); - this.datafileManager.on('update', this.onDatafileManagerUpdate.bind(this)); - } else if (this.configObj) { - this.readyPromise = Promise.resolve({ - success: true, - }); - } else { - this.readyPromise = Promise.resolve({ - success: false, - reason: getErrorMessage(handleNewDatafileException, 'Invalid datafile'), - }); - } - } catch (ex) { - logger.error(ex); - this.readyPromise = Promise.resolve({ - success: false, - reason: getErrorMessage(ex, 'Error in initialize'), - }); - } - } - - /** - * Respond to datafile manager's onReady promise becoming fulfilled. - * If there are validation or parse failures using the datafile provided by - * DatafileManager, ProjectConfigManager's ready promise is resolved with an - * unsuccessful result. Otherwise, ProjectConfigManager updates its own project - * config object from the new datafile, and its ready promise is resolved with a - * successful result. - */ - private onDatafileManagerReadyFulfill(): OnReadyResult { - if (this.datafileManager) { - const newDatafileError = this.handleNewDatafile(this.datafileManager.get()); - if (newDatafileError) { - return { - success: false, - reason: getErrorMessage(newDatafileError), - }; - } - return { success: true }; - } - - return { - success: false, - reason: getErrorMessage(null, 'Datafile manager is not provided'), - }; - } - - /** - * Respond to datafile manager's onReady promise becoming rejected. - * When DatafileManager's onReady promise is rejected, there is no possibility - * of obtaining a datafile. In this case, ProjectConfigManager's ready promise - * is fulfilled with an unsuccessful result. - * @param {Error} err - * @returns {Object} - */ - private onDatafileManagerReadyReject(err: Error): OnReadyResult { - return { - success: false, - reason: getErrorMessage(err, 'Failed to become ready'), - }; - } - - /** - * Respond to datafile manager's update event. Attempt to update own config - * object using latest datafile from datafile manager. Call own registered - * update listeners if successful - */ - private onDatafileManagerUpdate(): void { - if (this.datafileManager) { - this.handleNewDatafile(this.datafileManager.get()); - } - } - - /** - * Handle new datafile by attemping to create a new Project Config object. If successful and - * the new config object's revision is newer than the current one, sets/updates the project config - * and optimizely config object instance variables and returns null for the error. If unsuccessful, - * the project config and optimizely config objects will not be updated, and the error is returned. - * @param {string | object} newDatafile - * @returns {Error|null} error or null - */ - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - private handleNewDatafile(newDatafile: string | object): Error | null { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: newDatafile, - jsonSchemaValidator: this.jsonSchemaValidator, - logger: logger, - }); - - if (error) { - logger.error(error); - } else { - const oldRevision = this.configObj ? this.configObj.revision : 'null'; - if (configObj && oldRevision !== configObj.revision) { - this.configObj = configObj; - this.optimizelyConfigObj = null; - scheduleMicrotaskOrTimeout(() => { - this.updateListeners.forEach(listener => listener(configObj)); - }) - } - } - - return error; - } - - /** - * Returns the current project config object, or null if no project config object - * is available - * @return {ProjectConfig|null} - */ - getConfig(): ProjectConfig | null { - return this.configObj; - } - - /** - * Returns the optimizely config object or null - * @return {OptimizelyConfig|null} - */ - getOptimizelyConfig(): OptimizelyConfig | null { - if (!this.optimizelyConfigObj && this.configObj) { - this.optimizelyConfigObj = createOptimizelyConfig(this.configObj, toDatafile(this.configObj), logger); - } - return this.optimizelyConfigObj; - } - - /** - * Returns a Promise that fulfills when this ProjectConfigManager is ready to - * use (meaning it has a valid project config object), or has failed to become - * ready. - * - * Failure can be caused by the following: - * - At least one of sdkKey or datafile is not provided in the constructor argument - * - The provided datafile was invalid - * - The datafile provided by the datafile manager was invalid - * - The datafile manager failed to fetch a datafile - * - * The returned Promise is fulfilled with a result object containing these - * properties: - * - success (boolean): True if this instance is ready to use with a valid - * project config object, or false if it failed to - * become ready - * - reason (string=): If success is false, this is a string property with - * an explanatory message. - * @return {Promise} - */ - onReady(): Promise<OnReadyResult> { - return this.readyPromise; - } - - /** - * Add a listener for project config updates. The listener will be called - * whenever this instance has a new project config object available. - * Returns a dispose function that removes the subscription - * @param {Function} listener - * @return {Function} - */ - onUpdate(listener: (config: ProjectConfig) => void): () => void { - this.updateListeners.push(listener); - return () => { - const index = this.updateListeners.indexOf(listener); - if (index > -1) { - this.updateListeners.splice(index, 1); - } - }; - } - - /** - * Stop the internal datafile manager and remove all update listeners - */ - stop(): void { - if (this.datafileManager) { - this.datafileManager.stop(); - } - this.updateListeners = []; - } -} - -export function createProjectConfigManager(config: ProjectConfigManagerConfig): ProjectConfigManager { - return new ProjectConfigManager(config); -} diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index e14b91463..8b43a4902 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -33,6 +33,8 @@ import { OdpConfig } from './core/odp/odp_config'; import { BrowserOdpEventManager } from './plugins/odp/event_manager/index.browser'; import { BrowserOdpEventApiManager } from './plugins/odp/event_api_manager/index.browser'; import { OdpEvent } from './core/odp/odp_event'; +import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { createProjectConfig } from './project_config/project_config'; var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; @@ -123,12 +125,10 @@ describe('javascript-sdk (Browser)', function() { describe('when an eventDispatcher is not passed in', function() { it('should wrap the default eventDispatcher and invoke sendPendingEvents', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function() {}); sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); }); @@ -137,13 +137,11 @@ describe('javascript-sdk (Browser)', function() { describe('when an eventDispatcher is passed in', function() { it('should NOT wrap the default eventDispatcher and invoke sendPendingEvents', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function() {}); sinon.assert.notCalled(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); }); @@ -151,17 +149,15 @@ describe('javascript-sdk (Browser)', function() { it('should invoke resendPendingEvents at most once', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function() {}); sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, logger: silentLogger, }); @@ -174,23 +170,19 @@ describe('javascript-sdk (Browser)', function() { configValidator.validate.throws(new Error('Invalid config or something')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function() {}); }); }); it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); assert.equal(optlyInstance.clientVersion, '5.3.4'); @@ -198,13 +190,12 @@ describe('javascript-sdk (Browser)', function() { it('should set the JavaScript client engine and version', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function() {}); + assert.equal('javascript-sdk', optlyInstance.clientEngine); assert.equal(packageJSON.version, optlyInstance.clientVersion); }); @@ -212,19 +203,19 @@ describe('javascript-sdk (Browser)', function() { it('should allow passing of "react-sdk" as the clientEngine', function() { var optlyInstance = optimizelyFactory.createInstance({ clientEngine: 'react-sdk', - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function() {}); assert.equal('react-sdk', optlyInstance.clientEngine); }); it('should activate with provided event dispatcher', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -235,7 +226,9 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set and get a forced variation', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -250,7 +243,9 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set and unset a forced variation', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -271,7 +266,9 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set multiple experiments for one user', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -296,7 +293,9 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set multiple experiments for one user, and unset one', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -324,7 +323,9 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set multiple experiments for one user, and reset one', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -356,7 +357,9 @@ describe('javascript-sdk (Browser)', function() { it('should override bucketing when setForcedVariation is called', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -377,7 +380,9 @@ describe('javascript-sdk (Browser)', function() { it('should override bucketing when setForcedVariation is called for a not running experiment', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, logger: silentLogger, @@ -656,7 +661,10 @@ describe('javascript-sdk (Browser)', function() { it('should include the VUID instantation promise of Browser ODP Manager in the Optimizely client onReady promise dependency array', () => { const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + onRunning: Promise.resolve(), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -688,7 +696,10 @@ describe('javascript-sdk (Browser)', function() { it('should accept a valid custom cache size', () => { const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + onRunning: Promise.resolve(), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -762,8 +773,14 @@ describe('javascript-sdk (Browser)', function() { updateSettings: sinon.spy(), }; + const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -773,13 +790,12 @@ describe('javascript-sdk (Browser)', function() { }, }); + projectConfigManager.pushUpdate(config); + const readyData = await client.onReady(); sinon.assert.called(fakeSegmentManager.updateSettings); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); - const segments = await client.fetchQualifiedSegments(testVuid); assert.deepEqual(segments, ['a']); @@ -798,8 +814,14 @@ describe('javascript-sdk (Browser)', function() { sendEvent: sinon.spy(), }; + const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -809,9 +831,9 @@ describe('javascript-sdk (Browser)', function() { eventManager: fakeEventManager, }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + + await client.onReady(); sinon.assert.called(fakeEventManager.start); }); @@ -827,8 +849,14 @@ describe('javascript-sdk (Browser)', function() { flush: sinon.spy(), }; + const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -838,9 +866,8 @@ describe('javascript-sdk (Browser)', function() { }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + await client.onReady(); client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); @@ -867,8 +894,14 @@ describe('javascript-sdk (Browser)', function() { }), }; + const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -878,10 +911,8 @@ describe('javascript-sdk (Browser)', function() { eventRequestHandler: fakeRequestHandler, }, }); - const readyData = await client.onReady(); - - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + await client.onReady(); client.sendOdpEvent('test', '', new Map([['eamil', 'test@test.test']]), new Map([['key', 'value']])); clock.tick(10000); @@ -905,9 +936,14 @@ describe('javascript-sdk (Browser)', function() { sendEvent: sinon.spy(), flush: sinon.spy(), }; + const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -917,9 +953,8 @@ describe('javascript-sdk (Browser)', function() { }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + await client.onReady(); // fs-user-id client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['fs-user-id', 'fsUserA']])); @@ -977,8 +1012,15 @@ describe('javascript-sdk (Browser)', function() { flush: sinon.spy(), }; + const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + + const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -988,9 +1030,8 @@ describe('javascript-sdk (Browser)', function() { }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + await client.onReady(); client.sendOdpEvent(''); sinon.assert.called(logger.error); @@ -1015,8 +1056,14 @@ describe('javascript-sdk (Browser)', function() { flush: sinon.spy(), }; + const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -1025,10 +1072,8 @@ describe('javascript-sdk (Browser)', function() { eventManager: fakeEventManager, }, }); - - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + await client.onReady(); client.sendOdpEvent('dummy-action', ''); @@ -1039,8 +1084,14 @@ describe('javascript-sdk (Browser)', function() { }); it('should log an error when attempting to send an odp event when odp is disabled', async () => { + const config = createProjectConfig(testData.getTestProjectConfigWithFeatures()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -1050,9 +1101,10 @@ describe('javascript-sdk (Browser)', function() { }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + + await client.onReady(); + assert.isUndefined(client.odpManager); sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); @@ -1065,8 +1117,14 @@ describe('javascript-sdk (Browser)', function() { }); it('should log a warning when attempting to use an event batch size other than 1', async () => { + const config = createProjectConfig(testData.getTestProjectConfigWithFeatures()); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile: testData.getOdpIntegratedConfigWithSegments(), + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -1076,9 +1134,9 @@ describe('javascript-sdk (Browser)', function() { }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + + await client.onReady(); client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); @@ -1103,9 +1161,14 @@ describe('javascript-sdk (Browser)', function() { }); let datafile = testData.getOdpIntegratedConfigWithSegments(); + const config = createProjectConfig(datafile); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); const client = optimizelyFactory.createInstance({ - datafile, + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -1116,9 +1179,8 @@ describe('javascript-sdk (Browser)', function() { }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + await client.onReady(); client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); @@ -1157,8 +1219,14 @@ describe('javascript-sdk (Browser)', function() { logger, }); const datafile = testData.getOdpIntegratedConfigWithSegments(); + const config = createProjectConfig(datafile); + const projectConfigManager = getMockProjectConfigManager({ + initConfig: config, + onRunning: Promise.resolve(), + }); + const client = optimizelyFactory.createInstance({ - datafile, + projectConfigManager, errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -1169,9 +1237,8 @@ describe('javascript-sdk (Browser)', function() { }, }); - const readyData = await client.onReady(); - assert.equal(readyData.success, true); - assert.isUndefined(readyData.reason); + projectConfigManager.pushUpdate(config); + await client.onReady(); clock.tick(100); diff --git a/lib/index.browser.ts b/lib/index.browser.ts index c0d62897c..f80a7b2c3 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -27,12 +27,13 @@ import eventProcessorConfigValidator from './utils/event_processor_config_valida import { createNotificationCenter } from './core/notification_center'; import { default as eventProcessor } from './plugins/event_processor'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; -import { createHttpPollingDatafileManager } from './plugins/datafile_manager/browser_http_polling_datafile_manager'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import Optimizely from './optimizely'; import { IUserAgentParser } from './core/odp/user_agent_parser'; import { getUserAgentParser } from './plugins/odp/user_agent_parser/index.browser'; import * as commonExports from './common_exports'; +import { PollingConfigManagerConfig } from './project_config/config_manager_factory'; +import { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -139,9 +140,6 @@ const createInstance = function(config: Config): Client | null { eventProcessor: eventProcessor.createEventProcessor(eventProcessorConfig), logger, errorHandler, - datafileManager: config.sdkKey - ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) - : undefined, notificationCenter, isValidInstance, odpManager: odpExplicitlyOff ? undefined @@ -198,6 +196,7 @@ export { OptimizelyDecideOption, IUserAgentParser, getUserAgentParser, + createPollingProjectConfigManager, }; export * from './common_exports'; @@ -215,6 +214,7 @@ export default { __internalResetRetryState, OptimizelyDecideOption, getUserAgentParser, + createPollingProjectConfigManager, }; export * from './export_types'; diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 30282dcf5..ba67811bd 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -21,6 +21,7 @@ import Optimizely from './optimizely'; import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.lite'; import configValidator from './utils/config_validator'; +import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; describe('optimizelyFactory', function() { describe('APIs', function() { @@ -56,24 +57,20 @@ describe('optimizelyFactory', function() { var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), logger: localLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function() {}); }); sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); }); it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); assert.equal(optlyInstance.clientVersion, '5.3.4'); diff --git a/lib/index.lite.ts b/lib/index.lite.ts index 730aab7af..b6a6bdfe9 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021-2022, Optimizely + * Copyright 2021-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import Optimizely from './optimizely'; import { createNotificationCenter } from './core/notification_center'; import { createForwardingEventProcessor } from './plugins/event_processor/forwarding_event_processor'; import { OptimizelyDecideOption, Client, ConfigLite } from './shared_types'; -import { createNoOpDatafileManager } from './plugins/datafile_manager/no_op_datafile_manager'; import * as commonExports from './common_exports'; const logger = getLogger(); @@ -78,7 +77,6 @@ setLogLevel(LogLevel.ERROR); ...config, logger, errorHandler, - datafileManager: createNoOpDatafileManager(), eventProcessor, notificationCenter, isValidInstance: isValidInstance, diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 98aac4c97..4acbdf5f6 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -23,6 +23,8 @@ import testData from './tests/test_data'; import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; +import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { createProjectConfig } from './project_config/project_config'; describe('optimizelyFactory', function() { describe('APIs', function() { @@ -58,11 +60,9 @@ describe('optimizelyFactory', function() { var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), logger: localLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function() {}); }); sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); }); @@ -71,23 +71,19 @@ describe('optimizelyFactory', function() { configValidator.validate.throws(new Error('Invalid config or something')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function() {}); }); sinon.assert.calledOnce(console.error); }); it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function() {}); assert.instanceOf(optlyInstance, Optimizely); assert.equal(optlyInstance.clientVersion, '5.3.4'); @@ -105,7 +101,9 @@ describe('optimizelyFactory', function() { it('should ignore invalid event flush interval and use default instead', function() { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, @@ -121,7 +119,9 @@ describe('optimizelyFactory', function() { it('should use default event flush interval when none is provided', function() { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, @@ -136,7 +136,9 @@ describe('optimizelyFactory', function() { it('should use provided event flush interval when valid', function() { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, @@ -152,7 +154,9 @@ describe('optimizelyFactory', function() { it('should ignore invalid event batch size and use default instead', function() { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, @@ -168,7 +172,9 @@ describe('optimizelyFactory', function() { it('should use default event batch size when none is provided', function() { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, @@ -183,7 +189,9 @@ describe('optimizelyFactory', function() { it('should use provided event batch size when valid', function() { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, logger: fakeLogger, diff --git a/lib/index.node.ts b/lib/index.node.ts index 50a7829b8..0bb12d21e 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2016-2017, 2019-2024 Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * https://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2016-2017, 2019-2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; import Optimizely from './optimizely'; @@ -25,9 +25,9 @@ import eventProcessorConfigValidator from './utils/event_processor_config_valida import { createNotificationCenter } from './core/notification_center'; import { createEventProcessor } from './plugins/event_processor'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { createHttpPollingDatafileManager } from './plugins/datafile_manager/http_polling_datafile_manager'; import { NodeOdpManager } from './plugins/odp_manager/index.node'; import * as commonExports from './common_exports'; +import { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; const logger = getLogger(); setLogLevel(LogLevel.ERROR); @@ -115,9 +115,6 @@ const createInstance = function(config: Config): Client | null { eventProcessor, logger, errorHandler, - datafileManager: config.sdkKey - ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) - : undefined, notificationCenter, isValidInstance, odpManager: odpExplicitlyOff ? undefined @@ -144,6 +141,7 @@ export { setLogLevel, createInstance, OptimizelyDecideOption, + createPollingProjectConfigManager }; export * from './common_exports'; @@ -158,6 +156,7 @@ export default { setLogLevel, createInstance, OptimizelyDecideOption, + createPollingProjectConfigManager }; export * from './export_types'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index ee5a1975c..3be9b300c 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -25,9 +25,9 @@ import eventProcessorConfigValidator from './utils/event_processor_config_valida import { createNotificationCenter } from './core/notification_center'; import { createEventProcessor } from './plugins/event_processor/index.react_native'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { createHttpPollingDatafileManager } from './plugins/datafile_manager/react_native_http_polling_datafile_manager'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import * as commonExports from './common_exports'; +import { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; import 'fast-text-encoding'; import 'react-native-get-random-values'; @@ -114,15 +114,6 @@ const createInstance = function(config: Config): Client | null { eventProcessor, logger, errorHandler, - datafileManager: config.sdkKey - ? createHttpPollingDatafileManager( - config.sdkKey, - logger, - config.datafile, - config.datafileOptions, - config.persistentCacheProvider, - ) - : undefined, notificationCenter, isValidInstance: isValidInstance, odpManager: odpExplicitlyOff ? undefined @@ -154,6 +145,7 @@ export { setLogLevel, createInstance, OptimizelyDecideOption, + createPollingProjectConfigManager, }; export * from './common_exports'; @@ -168,6 +160,7 @@ export default { setLogLevel, createInstance, OptimizelyDecideOption, + createPollingProjectConfigManager, }; export * from './export_types'; diff --git a/lib/modules/datafile-manager/backoffController.ts b/lib/modules/datafile-manager/backoffController.ts deleted file mode 100644 index 8021f8cbd..000000000 --- a/lib/modules/datafile-manager/backoffController.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT } from './config'; - -function randomMilliseconds(): number { - return Math.round(Math.random() * 1000); -} - -export default class BackoffController { - private errorCount = 0; - - getDelay(): number { - if (this.errorCount === 0) { - return 0; - } - const baseWaitSeconds = - BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT[ - Math.min(BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1, this.errorCount) - ]; - return baseWaitSeconds * 1000 + randomMilliseconds(); - } - - countError(): void { - if (this.errorCount < BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1) { - this.errorCount++; - } - } - - reset(): void { - this.errorCount = 0; - } -} diff --git a/lib/modules/datafile-manager/browserDatafileManager.ts b/lib/modules/datafile-manager/browserDatafileManager.ts deleted file mode 100644 index 84ab1b10b..000000000 --- a/lib/modules/datafile-manager/browserDatafileManager.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; - -export default class BrowserDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: false, - }; - } -} diff --git a/lib/modules/datafile-manager/browserRequest.ts b/lib/modules/datafile-manager/browserRequest.ts deleted file mode 100644 index ce47a63eb..000000000 --- a/lib/modules/datafile-manager/browserRequest.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AbortableRequest, Response, Headers } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import { getLogger } from '../logging'; - -const logger = getLogger('DatafileManager'); - -const GET_METHOD = 'GET'; -const READY_STATE_DONE = 4; - -function parseHeadersFromXhr(req: XMLHttpRequest): Headers { - const allHeadersString = req.getAllResponseHeaders(); - - if (allHeadersString === null) { - return {}; - } - - const headerLines = allHeadersString.split('\r\n'); - const headers: Headers = {}; - headerLines.forEach(headerLine => { - const separatorIndex = headerLine.indexOf(': '); - if (separatorIndex > -1) { - const headerName = headerLine.slice(0, separatorIndex); - const headerValue = headerLine.slice(separatorIndex + 2); - if (headerValue.length > 0) { - headers[headerName] = headerValue; - } - } - }); - return headers; -} - -function setHeadersInXhr(headers: Headers, req: XMLHttpRequest): void { - Object.keys(headers).forEach(headerName => { - const header = headers[headerName]; - req.setRequestHeader(headerName, header!); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const req = new XMLHttpRequest(); - - const responsePromise: Promise<Response> = new Promise((resolve, reject) => { - req.open(GET_METHOD, reqUrl, true); - - setHeadersInXhr(headers, req); - - req.onreadystatechange = (): void => { - if (req.readyState === READY_STATE_DONE) { - const statusCode = req.status; - if (statusCode === 0) { - reject(new Error('Request error')); - return; - } - - const headers = parseHeadersFromXhr(req); - const resp: Response = { - statusCode: req.status, - body: req.responseText, - headers, - }; - resolve(resp); - } - }; - - req.timeout = REQUEST_TIMEOUT_MS; - - req.ontimeout = (): void => { - logger.error('Request timed out'); - }; - - req.send(); - }); - - return { - responsePromise, - abort(): void { - req.abort(); - }, - }; -} diff --git a/lib/modules/datafile-manager/eventEmitter.ts b/lib/modules/datafile-manager/eventEmitter.ts deleted file mode 100644 index 9cc2d8fbe..000000000 --- a/lib/modules/datafile-manager/eventEmitter.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DatafileUpdate } from "./datafileManager"; - -export type Disposer = () => void; - -export type Listener = (arg?: any) => void; - -interface Listeners { - [index: string]: { - // index is event name - [index: string]: Listener; // index is listener id - }; -} - -export default class EventEmitter { - private listeners: Listeners = {}; - - private listenerId = 1; - - on(eventName: string, listener: Listener): Disposer { - if (!this.listeners[eventName]) { - this.listeners[eventName] = {}; - } - const currentListenerId = String(this.listenerId); - this.listenerId++; - this.listeners[eventName][currentListenerId] = listener; - return (): void => { - if (this.listeners[eventName]) { - delete this.listeners[eventName][currentListenerId]; - } - }; - } - - emit(eventName: string, arg?: DatafileUpdate): void { - const listeners = this.listeners[eventName]; - if (listeners) { - Object.keys(listeners).forEach(listenerId => { - const listener = listeners[listenerId]; - listener(arg); - }); - } - } - - removeAllListeners(): void { - this.listeners = {}; - } -} - -// TODO: Create a typed event emitter for use in TS only (not JS) diff --git a/lib/modules/datafile-manager/http.ts b/lib/modules/datafile-manager/http.ts deleted file mode 100644 index d4505dc59..000000000 --- a/lib/modules/datafile-manager/http.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Headers is the interface that bridges between the abstract datafile manager and - * any Node-or-browser-specific http header types. - * It's simplified and can only store one value per header name. - * We can extend or replace this type if requirements change and we need - * to work with multiple values per header name. - */ -export interface Headers { - [header: string]: string | undefined; -} - -export interface Response { - statusCode?: number; - body: string; - headers: Headers; -} - -export interface AbortableRequest { - abort(): void; - responsePromise: Promise<Response>; -} diff --git a/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/lib/modules/datafile-manager/httpPollingDatafileManager.ts deleted file mode 100644 index 6dfce4c37..000000000 --- a/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ /dev/null @@ -1,348 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '../logging'; -import { sprintf } from '../../utils/fns'; -import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; -import EventEmitter, { Disposer } from './eventEmitter'; -import { AbortableRequest, Response, Headers } from './http'; -import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './config'; -import BackoffController from './backoffController'; -import PersistentKeyValueCache from '../../plugins/key_value_cache/persistentKeyValueCache'; - -import { NotificationRegistry } from './../../core/notification_center/notification_registry'; -import { NOTIFICATION_TYPES } from '../../utils/enums'; - -const logger = getLogger('DatafileManager'); - -const UPDATE_EVT = 'update'; - -function isSuccessStatusCode(statusCode: number): boolean { - return statusCode >= 200 && statusCode < 400; -} - -const noOpKeyValueCache: PersistentKeyValueCache = { - get(): Promise<string | undefined> { - return Promise.resolve(undefined); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<boolean> { - return Promise.resolve(false); - }, -}; - -export default abstract class HttpPollingDatafileManager implements DatafileManager { - // Make an HTTP get request to the given URL with the given headers - // Return an AbortableRequest, which has a promise for a Response. - // If we can't get a response, the promise is rejected. - // The request will be aborted if the manager is stopped while the request is in flight. - protected abstract makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest; - - // Return any default configuration options that should be applied - protected abstract getConfigDefaults(): Partial<DatafileManagerConfig>; - - private currentDatafile: string; - - private readonly readyPromise: Promise<void>; - - private isReadyPromiseSettled: boolean; - - private readyPromiseResolver: () => void; - - private readyPromiseRejecter: (err: Error) => void; - - private readonly emitter: EventEmitter; - - private readonly autoUpdate: boolean; - - private readonly updateInterval: number; - - private currentTimeout: any; - - private isStarted: boolean; - - private lastResponseLastModified?: string; - - private datafileUrl: string; - - private currentRequest: AbortableRequest | null; - - private backoffController: BackoffController; - - private cacheKey: string; - - private cache: PersistentKeyValueCache; - - private sdkKey: string; - - // When true, this means the update interval timeout fired before the current - // sync completed. In that case, we should sync again immediately upon - // completion of the current request, instead of waiting another update - // interval. - private syncOnCurrentRequestComplete: boolean; - - constructor(config: DatafileManagerConfig) { - const configWithDefaultsApplied: DatafileManagerConfig = { - ...this.getConfigDefaults(), - ...config, - }; - const { - datafile, - autoUpdate = false, - sdkKey, - updateInterval = DEFAULT_UPDATE_INTERVAL, - urlTemplate = DEFAULT_URL_TEMPLATE, - cache = noOpKeyValueCache, - } = configWithDefaultsApplied; - this.cache = cache; - this.cacheKey = 'opt-datafile-' + sdkKey; - this.sdkKey = sdkKey; - this.isReadyPromiseSettled = false; - this.readyPromiseResolver = (): void => { }; - this.readyPromiseRejecter = (): void => { }; - this.readyPromise = new Promise((resolve, reject) => { - this.readyPromiseResolver = resolve; - this.readyPromiseRejecter = reject; - }); - - if (datafile) { - this.currentDatafile = datafile; - if (!sdkKey) { - this.resolveReadyPromise(); - } - } else { - this.currentDatafile = ''; - } - - this.isStarted = false; - - this.datafileUrl = sprintf(urlTemplate, sdkKey); - - this.emitter = new EventEmitter(); - - this.autoUpdate = autoUpdate; - - this.updateInterval = updateInterval; - if (this.updateInterval < MIN_UPDATE_INTERVAL) { - logger.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE); - } - - this.currentTimeout = null; - - this.currentRequest = null; - - this.backoffController = new BackoffController(); - - this.syncOnCurrentRequestComplete = false; - } - - get(): string { - return this.currentDatafile; - } - - start(): void { - if (!this.isStarted) { - logger.debug('Datafile manager started'); - this.isStarted = true; - this.backoffController.reset(); - this.setDatafileFromCacheIfAvailable(); - this.syncDatafile(); - } - } - - stop(): Promise<void> { - logger.debug('Datafile manager stopped'); - this.isStarted = false; - if (this.currentTimeout) { - clearTimeout(this.currentTimeout); - this.currentTimeout = null; - } - - this.emitter.removeAllListeners(); - - if (this.currentRequest) { - this.currentRequest.abort(); - this.currentRequest = null; - } - - return Promise.resolve(); - } - - onReady(): Promise<void> { - return this.readyPromise; - } - - on(eventName: string, listener: (datafileUpdate: DatafileUpdate) => void): Disposer { - return this.emitter.on(eventName, listener); - } - - private onRequestRejected(err: any): void { - if (!this.isStarted) { - return; - } - - this.backoffController.countError(); - - if (err instanceof Error) { - logger.error('Error fetching datafile: %s', err.message, err); - } else if (typeof err === 'string') { - logger.error('Error fetching datafile: %s', err); - } else { - logger.error('Error fetching datafile'); - } - } - - private onRequestResolved(response: Response): void { - if (!this.isStarted) { - return; - } - - if (typeof response.statusCode !== 'undefined' && isSuccessStatusCode(response.statusCode)) { - this.backoffController.reset(); - } else { - this.backoffController.countError(); - } - - this.trySavingLastModified(response.headers); - - const datafile = this.getNextDatafileFromResponse(response); - if (datafile !== '') { - logger.info('Updating datafile from response'); - this.currentDatafile = datafile; - this.cache.set(this.cacheKey, datafile); - if (!this.isReadyPromiseSettled) { - this.resolveReadyPromise(); - } else { - const datafileUpdate: DatafileUpdate = { - datafile, - }; - NotificationRegistry.getNotificationCenter(this.sdkKey, logger)?.sendNotifications( - NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE - ); - this.emitter.emit(UPDATE_EVT, datafileUpdate); - } - } - } - - private onRequestComplete(this: HttpPollingDatafileManager): void { - if (!this.isStarted) { - return; - } - - this.currentRequest = null; - - if (!this.isReadyPromiseSettled && !this.autoUpdate) { - // We will never resolve ready, so reject it - this.rejectReadyPromise(new Error('Failed to become ready')); - } - - if (this.autoUpdate && this.syncOnCurrentRequestComplete) { - this.syncDatafile(); - } - this.syncOnCurrentRequestComplete = false; - } - - private syncDatafile(): void { - const headers: Headers = {}; - if (this.lastResponseLastModified) { - headers['if-modified-since'] = this.lastResponseLastModified; - } - - logger.debug('Making datafile request to url %s with headers: %s', this.datafileUrl, () => JSON.stringify(headers)); - this.currentRequest = this.makeGetRequest(this.datafileUrl, headers); - - const onRequestComplete = (): void => { - this.onRequestComplete(); - }; - const onRequestResolved = (response: Response): void => { - this.onRequestResolved(response); - }; - const onRequestRejected = (err: any): void => { - this.onRequestRejected(err); - }; - this.currentRequest.responsePromise - .then(onRequestResolved, onRequestRejected) - .then(onRequestComplete, onRequestComplete); - - if (this.autoUpdate) { - this.scheduleNextUpdate(); - } - } - - private resolveReadyPromise(): void { - this.readyPromiseResolver(); - this.isReadyPromiseSettled = true; - } - - private rejectReadyPromise(err: Error): void { - this.readyPromiseRejecter(err); - this.isReadyPromiseSettled = true; - } - - private scheduleNextUpdate(): void { - const currentBackoffDelay = this.backoffController.getDelay(); - const nextUpdateDelay = Math.max(currentBackoffDelay, this.updateInterval); - logger.debug('Scheduling sync in %s ms', nextUpdateDelay); - this.currentTimeout = setTimeout(() => { - if (this.currentRequest) { - this.syncOnCurrentRequestComplete = true; - } else { - this.syncDatafile(); - } - }, nextUpdateDelay); - } - - private getNextDatafileFromResponse(response: Response): string { - logger.debug('Response status code: %s', response.statusCode); - if (typeof response.statusCode === 'undefined') { - return ''; - } - if (response.statusCode === 304) { - return ''; - } - if (isSuccessStatusCode(response.statusCode)) { - return response.body; - } - logger.error(`Datafile fetch request failed with status: ${response.statusCode}`); - return ''; - } - - private trySavingLastModified(headers: Headers): void { - const lastModifiedHeader = headers['last-modified'] || headers['Last-Modified']; - if (typeof lastModifiedHeader !== 'undefined') { - this.lastResponseLastModified = lastModifiedHeader; - logger.debug('Saved last modified header value from response: %s', this.lastResponseLastModified); - } - } - - setDatafileFromCacheIfAvailable(): void { - this.cache.get(this.cacheKey).then(datafile => { - if (this.isStarted && !this.isReadyPromiseSettled && datafile) { - logger.debug('Using datafile from cache'); - this.currentDatafile = datafile; - this.resolveReadyPromise(); - } - }); - } -} diff --git a/lib/modules/datafile-manager/index.react_native.ts b/lib/modules/datafile-manager/index.react_native.ts deleted file mode 100644 index fa42e20ca..000000000 --- a/lib/modules/datafile-manager/index.react_native.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './reactNativeDatafileManager'; diff --git a/lib/modules/datafile-manager/nodeDatafileManager.ts b/lib/modules/datafile-manager/nodeDatafileManager.ts deleted file mode 100644 index d97e14920..000000000 --- a/lib/modules/datafile-manager/nodeDatafileManager.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '../logging'; -import { makeGetRequest } from './nodeRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { NodeDatafileManagerConfig, DatafileManagerConfig } from './datafileManager'; -import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './config'; - -const logger = getLogger('NodeDatafileManager'); - -export default class NodeDatafileManager extends HttpPollingDatafileManager { - private accessToken?: string; - - constructor(config: NodeDatafileManagerConfig) { - const defaultUrlTemplate = config.datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE; - super({ - ...config, - urlTemplate: config.urlTemplate || defaultUrlTemplate, - }); - this.accessToken = config.datafileAccessToken; - } - - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const requestHeaders = Object.assign({}, headers); - if (this.accessToken) { - logger.debug('Adding Authorization header with Bearer Token'); - requestHeaders['Authorization'] = `Bearer ${this.accessToken}`; - } - return makeGetRequest(reqUrl, requestHeaders); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - }; - } -} diff --git a/lib/modules/datafile-manager/nodeRequest.ts b/lib/modules/datafile-manager/nodeRequest.ts deleted file mode 100644 index 24ceed0e1..000000000 --- a/lib/modules/datafile-manager/nodeRequest.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import http from 'http'; -import https from 'https'; -import url from 'url'; -import { Headers, AbortableRequest, Response } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import decompressResponse from 'decompress-response'; - -// Shared signature between http.request and https.request -type ClientRequestCreator = (options: http.RequestOptions) => http.ClientRequest; - -function getRequestOptionsFromUrl(url: url.UrlWithStringQuery): http.RequestOptions { - return { - hostname: url.hostname, - path: url.path, - port: url.port, - protocol: url.protocol, - }; -} - -/** - * Convert incomingMessage.headers (which has type http.IncomingHttpHeaders) into our Headers type defined in src/http.ts. - * - * Our Headers type is simplified and can't represent mutliple values for the same header name. - * - * We don't currently need multiple values support, and the consumer code becomes simpler if it can assume at-most 1 value - * per header name. - * - */ -function createHeadersFromNodeIncomingMessage(incomingMessage: http.IncomingMessage): Headers { - const headers: Headers = {}; - Object.keys(incomingMessage.headers).forEach(headerName => { - const headerValue = incomingMessage.headers[headerName]; - if (typeof headerValue === 'string') { - headers[headerName] = headerValue; - } else if (typeof headerValue === 'undefined') { - // no value provided for this header - } else { - // array - if (headerValue.length > 0) { - // We don't care about multiple values - just take the first one - headers[headerName] = headerValue[0]; - } - } - }); - return headers; -} - -function getResponseFromRequest(request: http.ClientRequest): Promise<Response> { - // TODO: When we drop support for Node 6, consider using util.promisify instead of - // constructing own Promise - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - request.abort(); - reject(new Error('Request timed out')); - }, REQUEST_TIMEOUT_MS); - - request.once('response', (incomingMessage: http.IncomingMessage) => { - if (request.aborted) { - return; - } - - const response = decompressResponse(incomingMessage); - - response.setEncoding('utf8'); - - let responseData = ''; - response.on('data', (chunk: string) => { - if (!request.aborted) { - responseData += chunk; - } - }); - - response.on('end', () => { - if (request.aborted) { - return; - } - - clearTimeout(timeout); - - resolve({ - statusCode: incomingMessage.statusCode, - body: responseData, - headers: createHeadersFromNodeIncomingMessage(incomingMessage), - }); - }); - }); - - request.on('error', (err: any) => { - clearTimeout(timeout); - - if (err instanceof Error) { - reject(err); - } else if (typeof err === 'string') { - reject(new Error(err)); - } else { - reject(new Error('Request error')); - } - }); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - // TODO: Use non-legacy URL parsing when we drop support for Node 6 - const parsedUrl = url.parse(reqUrl); - - let requester: ClientRequestCreator; - if (parsedUrl.protocol === 'http:') { - requester = http.request; - } else if (parsedUrl.protocol === 'https:') { - requester = https.request; - } else { - return { - responsePromise: Promise.reject(new Error(`Unsupported protocol: ${parsedUrl.protocol}`)), - abort(): void {}, - }; - } - - const requestOptions: http.RequestOptions = { - ...getRequestOptionsFromUrl(parsedUrl), - method: 'GET', - headers: { - ...headers, - 'accept-encoding': 'gzip,deflate', - }, - }; - - const request = requester(requestOptions); - const responsePromise = getResponseFromRequest(request); - - request.end(); - - return { - abort(): void { - request.abort(); - }, - responsePromise, - }; -} diff --git a/lib/modules/datafile-manager/reactNativeDatafileManager.ts b/lib/modules/datafile-manager/reactNativeDatafileManager.ts deleted file mode 100644 index c3857c2fe..000000000 --- a/lib/modules/datafile-manager/reactNativeDatafileManager.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; -import ReactNativeAsyncStorageCache from '../../plugins/key_value_cache/reactNativeAsyncStorageCache'; - -export default class ReactNativeDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - cache: new ReactNativeAsyncStorageCache(), - }; - } -} diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 3f5b3e232..9047ee71a 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2016-2024, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2016-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { assert, expect } from 'chai'; import sinon from 'sinon'; import { sprintf } from '../utils/fns'; @@ -24,7 +24,7 @@ import OptimizelyUserContext from '../optimizely_user_context'; import { OptimizelyDecideOption } from '../shared_types'; import AudienceEvaluator from '../core/audience_evaluator'; import * as bucketer from '../core/bucketer'; -import * as projectConfigManager from '../core/project_config/project_config_manager'; +import * as projectConfigManager from '../project_config/project_config_manager'; import * as enums from '../utils/enums'; import eventDispatcher from '../plugins/event_dispatcher/index.node'; import errorHandler from '../plugins/error_handler'; @@ -32,13 +32,14 @@ import fns from '../utils/fns'; import * as logger from '../plugins/logger'; import * as decisionService from '../core/decision_service'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; -import * as projectConfig from '../core/project_config'; +import * as projectConfig from '../project_config/project_config'; import testData from '../tests/test_data'; import { createForwardingEventProcessor } from '../plugins/event_processor/forwarding_event_processor'; import { createEventProcessor } from '../plugins/event_processor'; import { createNotificationCenter } from '../core/notification_center'; -import { createHttpPollingDatafileManager } from '../plugins/datafile_manager/http_polling_datafile_manager'; import { NodeOdpManager } from '../plugins/odp_manager/index.node'; +import { createProjectConfig } from '../project_config/project_config'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; @@ -109,37 +110,9 @@ describe('lib/optimizely', function() { }); describe('constructor', function() { - it('should construct an instance of the Optimizely class', function() { - var optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor, - }); - assert.instanceOf(optlyInstance, Optimizely); - }); - - it('should construct an instance of the Optimizely class when datafile is JSON string', function() { - var optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: JSON.stringify(testData.getTestProjectConfig()), - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor, - }); - assert.instanceOf(optlyInstance, Optimizely); - }); - it('should log if the client engine passed in is invalid', function() { new Optimizely({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager(), errorHandler: stubErrorHandler, eventDispatcher: stubEventDispatcher, logger: createdLogger, @@ -154,8 +127,8 @@ describe('lib/optimizely', function() { it('should log if the defaultDecideOptions passed in are invalid', function() { new Optimizely({ + projectConfigManager: getMockProjectConfigManager(), clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), errorHandler: stubErrorHandler, eventDispatcher: stubEventDispatcher, logger: createdLogger, @@ -171,8 +144,8 @@ describe('lib/optimizely', function() { it('should allow passing `react-sdk` as the clientEngine', function() { var instance = new Optimizely({ + projectConfigManager: getMockProjectConfigManager(), clientEngine: 'react-sdk', - datafile: testData.getTestProjectConfig(), errorHandler: stubErrorHandler, eventDispatcher: stubEventDispatcher, logger: createdLogger, @@ -201,7 +174,7 @@ describe('lib/optimizely', function() { new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager(), jsonSchemaValidator: jsonSchemaValidator, userProfileService: userProfileServiceInstance, notificationCenter, @@ -226,7 +199,7 @@ describe('lib/optimizely', function() { new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager(), jsonSchemaValidator: jsonSchemaValidator, userProfileService: invalidUserProfile, notificationCenter, @@ -246,80 +219,6 @@ describe('lib/optimizely', function() { ); }); }); - - describe('when an sdkKey is provided', function() { - it('should not log an error when sdkKey is provided and datafile is not provided', function() { - new Optimizely({ - clientEngine: 'node-sdk', - errorHandler: stubErrorHandler, - eventDispatcher: eventDispatcher, - isValidInstance: true, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', createdLogger), - notificationCenter, - eventProcessor, - }); - sinon.assert.notCalled(stubErrorHandler.handleError); - }); - - it('passes datafile, datafileOptions, sdkKey, and other options to the project config manager', function() { - var config = testData.getTestProjectConfig(); - let datafileOptions = { - autoUpdate: true, - updateInterval: 2 * 60 * 1000, - }; - let datafileManager = createHttpPollingDatafileManager('12345', createdLogger, undefined, datafileOptions); - new Optimizely({ - clientEngine: 'node-sdk', - datafile: config, - datafileOptions: datafileOptions, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - isValidInstance: true, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - sdkKey: '12345', - datafileManager: datafileManager, - notificationCenter, - eventProcessor, - }); - sinon.assert.calledOnce(projectConfigManager.createProjectConfigManager); - sinon.assert.calledWithExactly(projectConfigManager.createProjectConfigManager, { - datafile: config, - jsonSchemaValidator: jsonSchemaValidator, - sdkKey: '12345', - datafileManager: datafileManager, - }); - }); - }); - - it('should support constructing two instances using the same datafile object', function() { - var datafile = testData.getTypedAudiencesConfig(); - var optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: datafile, - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor, - }); - assert.instanceOf(optlyInstance, Optimizely); - var optlyInstance2 = new Optimizely({ - clientEngine: 'node-sdk', - datafile: datafile, - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor, - }); - assert.instanceOf(optlyInstance2, Optimizely); - }); }); }); @@ -334,9 +233,14 @@ describe('lib/optimizely', function() { logToConsole: false, }); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + // datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -972,8 +876,13 @@ describe('lib/optimizely', function() { reasons: [], }; bucketStub.returns(fakeDecisionResponse); + + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + var instance = new Optimizely({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -1061,7 +970,7 @@ describe('lib/optimizely', function() { it('should not activate when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -1748,8 +1657,12 @@ describe('lib/optimizely', function() { }); it('should track when logger is in DEBUG mode', function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + var instance = new Optimizely({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -1769,7 +1682,7 @@ describe('lib/optimizely', function() { it('should not track when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -1936,7 +1849,7 @@ describe('lib/optimizely', function() { it('should not return variation when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -2772,9 +2685,13 @@ describe('lib/optimizely', function() { describe('activate', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -2831,9 +2748,13 @@ describe('lib/optimizely', function() { describe('getVariation', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -2883,9 +2804,13 @@ describe('lib/optimizely', function() { }); it('should send notification with variation key and type feature-test when getVariation returns feature experiment variation', function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + var optly = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -2920,9 +2845,13 @@ describe('lib/optimizely', function() { var sandbox = sinon.sandbox.create(); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -4542,9 +4471,13 @@ describe('lib/optimizely', function() { describe('#createUserContext', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -4647,9 +4580,13 @@ describe('lib/optimizely', function() { var userId = 'tester'; describe('with empty default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -4695,7 +4632,7 @@ describe('lib/optimizely', function() { }); it('should return error decision object when SDK is not ready and do not dispatch an event', function() { - optlyInstance.projectConfigManager.getConfig.returns(null); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(null); var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -4938,7 +4875,7 @@ describe('lib/optimizely', function() { it('should make a decision for rollout and do not dispatch an event when sendFlagDecisions is set to false', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = false; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var flagKey = 'feature_1'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -5023,9 +4960,13 @@ describe('lib/optimizely', function() { describe('with EXCLUDE_VARIABLES flag in default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5133,9 +5074,13 @@ describe('lib/optimizely', function() { describe('with DISABLE_DECISION_EVENT flag in default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5198,9 +5143,13 @@ describe('lib/optimizely', function() { describe('with INCLUDE_REASONS flag in default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5222,7 +5171,7 @@ describe('lib/optimizely', function() { it('should include reason when experiment is not running', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].status = 'NotRunning'; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -5251,9 +5200,13 @@ describe('lib/optimizely', function() { }), save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + var optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5280,7 +5233,7 @@ describe('lib/optimizely', function() { var variationKey = 'b'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].forcedVariations[userId] = variationKey; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5298,7 +5251,7 @@ describe('lib/optimizely', function() { optlyInstance.decisionService.forcedVariationMap[userId] = { '10390977673': variationKey }; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.variationIdMap[variationKey] = { key: variationKey }; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5314,7 +5267,7 @@ describe('lib/optimizely', function() { var variationKey = 'invalid-key'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].forcedVariations[userId] = variationKey; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5410,7 +5363,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[1].trafficAllocation = []; newConfig.experiments[1].trafficAllocation.push({ endOfRange: 0, entityId: 'any' }); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5429,7 +5382,7 @@ describe('lib/optimizely', function() { var groupId = '13142870430'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.featureFlags[2].experimentIds.push(experimentId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5444,7 +5397,7 @@ describe('lib/optimizely', function() { var flagKey = 'feature_3'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.groups[0].trafficAllocation = []; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5475,7 +5428,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5499,7 +5452,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5524,7 +5477,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5549,7 +5502,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5574,7 +5527,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5599,7 +5552,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5624,7 +5577,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5649,7 +5602,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5681,9 +5634,13 @@ describe('lib/optimizely', function() { }), save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5724,9 +5681,13 @@ describe('lib/optimizely', function() { }), save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5771,9 +5732,13 @@ describe('lib/optimizely', function() { }), save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5802,9 +5767,13 @@ describe('lib/optimizely', function() { describe('#decideForKeys', function() { var userId = 'tester'; beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -5909,9 +5878,13 @@ describe('lib/optimizely', function() { var userId = 'tester'; describe('with empty default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -6011,9 +5984,13 @@ describe('lib/optimizely', function() { describe('with ENABLED_FLAGS_ONLY flag in default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -6113,9 +6090,13 @@ describe('lib/optimizely', function() { notificationCenter: notificationCenter, }); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -6176,9 +6157,13 @@ describe('lib/optimizely', function() { }); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -6209,10 +6194,7 @@ describe('lib/optimizely', function() { it('returns false if the instance is invalid', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: { - lasers: 300, - message: 'this is not a valid datafile', - }, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -6655,7 +6637,7 @@ describe('lib/optimizely', function() { it('returns false and does not dispatch an event when sendFlagDecisions is not defined', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = undefined; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); @@ -6668,7 +6650,7 @@ describe('lib/optimizely', function() { it('returns false and does not dispatch an event when sendFlagDecisions is set to false', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = false; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); @@ -6681,7 +6663,7 @@ describe('lib/optimizely', function() { it('returns false and dispatch an event when sendFlagDecisions is set to true', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = true; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); @@ -6753,10 +6735,7 @@ describe('lib/optimizely', function() { it('returns an empty array if the instance is invalid', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: { - lasers: 300, - message: 'this is not a valid datafile', - }, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -6794,9 +6773,13 @@ describe('lib/optimizely', function() { }); it('return features that are enabled for the user and send notification for every feature', function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -8874,7 +8857,7 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariable when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -8893,7 +8876,7 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableBoolean when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -8912,7 +8895,7 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableDouble when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -8931,7 +8914,7 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableInteger when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -8950,7 +8933,7 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableString when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -8969,7 +8952,7 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableJSON when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, @@ -9002,9 +8985,13 @@ describe('lib/optimizely', function() { notificationCenter: notificationCenter, }); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTypedAudiencesConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTypedAudiencesConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -9142,9 +9129,13 @@ describe('lib/optimizely', function() { notificationCenter: notificationCenter, }); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTypedAudiencesConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTypedAudiencesConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -9354,9 +9345,13 @@ describe('lib/optimizely', function() { var optlyInstance; beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -9650,9 +9645,13 @@ describe('lib/optimizely', function() { beforeEach(function() { eventProcessorStopPromise = Promise.resolve(); mockEventProcessor.stop.returns(eventProcessorStopPromise); + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -9682,9 +9681,13 @@ describe('lib/optimizely', function() { beforeEach(function() { eventProcessorStopPromise = Promise.reject(new Error('Failed to stop')); mockEventProcessor.stop.returns(eventProcessorStopPromise); + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -9741,9 +9744,13 @@ describe('lib/optimizely', function() { var optlyInstance; it('should call the project config manager stop method when the close method is called', function() { + const projectConfigManager = getMockProjectConfigManager(); + sinon.stub(projectConfigManager, 'stop'); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, + projectConfigManager, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9753,14 +9760,14 @@ describe('lib/optimizely', function() { notificationCenter, }); optlyInstance.close(); - var fakeManager = projectConfigManager.createProjectConfigManager.getCall(0).returnValue; - sinon.assert.calledOnce(fakeManager.stop); + sinon.assert.calledOnce(projectConfigManager.stop); }); - describe('when no datafile is available yet ', function() { + describe('when no project config is available yet ', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', + projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -9808,18 +9815,12 @@ describe('lib/optimizely', function() { clearTimeoutSpy.restore(); }); - it('fulfills the promise with the value from the project config manager ready promise after the project config manager ready promise is fulfilled', function() { - projectConfigManager.createProjectConfigManager.callsFake(function(config) { - var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; - return { - stop: sinon.stub(), - getConfig: sinon.stub().returns(currentConfig), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve({ success: true })), - }; - }); + it('fulfills the promise after the project config manager onRunning promise is fulfilled', function() { + const projectConfigManager = getMockProjectConfigManager(); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', + projectConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -9829,15 +9830,17 @@ describe('lib/optimizely', function() { notificationCenter, eventProcessor, }); - return optlyInstance.onReady().then(function(result) { - assert.deepEqual(result, { success: true }); - }); + + return optlyInstance.onReady(); }); - it('fulfills the promise with an unsuccessful result after the timeout has expired when the project config manager onReady promise still has not resolved', function() { + it('rejects the promise after the timeout has expired when the project config manager onReady promise still has not resolved', function() { + const projectConfigManager = getMockProjectConfigManager({ onRunning: new Promise(function() {}) }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, + projectConfigManager, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9848,17 +9851,20 @@ describe('lib/optimizely', function() { }); var readyPromise = optlyInstance.onReady({ timeout: 500 }); clock.tick(501); - return readyPromise.then(function(result) { - assert.include(result, { - success: false, - }); + return readyPromise.then(() => { + return Promise.reject(new Error('Promise should not have resolved')); + }, (err) => { + assert.equal(err.message, 'onReady timeout expired after 500 ms') }); }); - it('fulfills the promise with an unsuccessful result after 30 seconds when no timeout argument is provided and the project config manager onReady promise still has not resolved', function() { + it('rejects the promise after 30 seconds when no timeout argument is provided and the project config manager onReady promise still has not resolved', function() { + const projectConfigManager = getMockProjectConfigManager({ onRunning: new Promise(function() {}) }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, + projectConfigManager, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9869,17 +9875,20 @@ describe('lib/optimizely', function() { }); var readyPromise = optlyInstance.onReady(); clock.tick(300001); - return readyPromise.then(function(result) { - assert.include(result, { - success: false, - }); + return readyPromise.then(() => { + return Promise.reject(new Error('Promise should not have resolved')); + }, (err) => { + assert.equal(err.message, 'onReady timeout expired after 30000 ms') }); }); - it('fulfills the promise with an unsuccessful result after the instance is closed', function() { + it('rejects the promise after the instance is closed', function() { + const projectConfigManager = getMockProjectConfigManager({ onRunning: new Promise(function() {}) }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, + projectConfigManager, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9890,10 +9899,10 @@ describe('lib/optimizely', function() { }); var readyPromise = optlyInstance.onReady({ timeout: 100 }); optlyInstance.close(); - return readyPromise.then(function(result) { - assert.include(result, { - success: false, - }); + return readyPromise.then(() => { + return Promise.reject(new Error('Promise should not have resolved')); + }, (err) => { + assert.equal(err.message, 'Instance closed') }); }); @@ -9901,6 +9910,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9927,17 +9937,10 @@ describe('lib/optimizely', function() { }); it('clears the timeout when the project config manager ready promise fulfills', function() { - projectConfigManager.createProjectConfigManager.callsFake(function(config) { - return { - stop: sinon.stub(), - getConfig: sinon.stub().returns(null), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve({ success: true })), - }; - }); optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9958,18 +9961,13 @@ describe('lib/optimizely', function() { describe('project config updates', function() { var fakeProjectConfigManager; beforeEach(function() { - fakeProjectConfigManager = { - stop: sinon.stub(), - getConfig: sinon.stub().returns(null), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns({ then: function() {} }), - }; - projectConfigManager.createProjectConfigManager.returns(fakeProjectConfigManager); + fakeProjectConfigManager = getMockProjectConfigManager(), optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, eventDispatcher: eventDispatcher, + projectConfigManager: fakeProjectConfigManager, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9986,10 +9984,13 @@ describe('lib/optimizely', function() { assert.isNull(optlyInstance.activate('myOtherExperiment', 'user98765')); // Project config manager receives new project config object - should use this now - var newConfig = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - fakeProjectConfigManager.getConfig.returns(newConfig); - var updateListener = fakeProjectConfigManager.onUpdate.getCall(0).args[0]; - updateListener(newConfig); + + const datafile = testData.getTestProjectConfigWithFeatures(); + + const newConfig = createProjectConfig(datafile, JSON.stringify(datafile)); + + fakeProjectConfigManager.setConfig(newConfig); + fakeProjectConfigManager.pushUpdate(newConfig); // With the new project config containing this feature, should return true assert.isTrue(optlyInstance.isFeatureEnabled('test_feature_for_experiment', 'user45678')); @@ -10017,9 +10018,9 @@ describe('lib/optimizely', function() { ], }); differentDatafile.revision = '44'; - var differentConfig = projectConfig.createProjectConfig(differentDatafile); - fakeProjectConfigManager.getConfig.returns(differentConfig); - updateListener(differentConfig); + var differentConfig = createProjectConfig(differentDatafile, JSON.stringify(differentDatafile)); + fakeProjectConfigManager.setConfig(differentConfig); + fakeProjectConfigManager.pushUpdate(differentConfig); // activate should return a variation for the new experiment assert.strictEqual(optlyInstance.activate('myOtherExperiment', 'user98765'), 'control'); @@ -10031,9 +10032,9 @@ describe('lib/optimizely', function() { enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, listener ); - var newConfig = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - var updateListener = fakeProjectConfigManager.onUpdate.getCall(0).args[0]; - updateListener(newConfig); + var newConfig = createProjectConfig(testData.getTestProjectConfigWithFeatures()); + fakeProjectConfigManager.pushUpdate(newConfig); + sinon.assert.calledOnce(listener); }); }); @@ -10056,9 +10057,14 @@ describe('lib/optimizely', function() { batchSize: 1, notificationCenter: notificationCenter, }); + + const datafile = testData.getTestProjectConfig(); + const mockConfigManager = getMockProjectConfigManager(); + mockConfigManager.setConfig(createProjectConfig(datafile, JSON.stringify(datafile))); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler, logger, isValidInstance: true, @@ -10117,9 +10123,13 @@ describe('lib/optimizely', function() { }); beforeEach(function() { + const datafile = testData.getTestProjectConfig(); + const mockConfigManager = getMockProjectConfigManager(); + mockConfigManager.setConfig(createProjectConfig(datafile, JSON.stringify(datafile))); + optlyInstanceWithOdp = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + projectConfigManager: mockConfigManager, errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 2a3eb5a0d..95d3682a3 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2020-2024, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * https://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2020-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; @@ -41,15 +41,14 @@ import { } from '../shared_types'; import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; -import { createProjectConfigManager, ProjectConfigManager } from '../core/project_config/project_config_manager'; +import { ProjectConfigManager } from '../project_config/project_config_manager'; import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; import { getImpressionEvent, getConversionEvent } from '../core/event_builder'; import { buildImpressionEvent, buildConversionEvent } from '../core/event_builder/event_helpers'; -import { NotificationRegistry } from '../core/notification_center/notification_registry'; import fns from '../utils/fns'; import { validate } from '../utils/attributes_validator'; import * as eventTagsValidator from '../utils/event_tags_validator'; -import * as projectConfig from '../core/project_config'; +import * as projectConfig from '../project_config/project_config'; import * as userProfileServiceValidator from '../utils/user_profile_service_validator'; import * as stringValidator from '../utils/string_value_validator'; import * as decision from '../core/decision'; @@ -69,6 +68,9 @@ import { FS_USER_ID_ALIAS, ODP_USER_KEY, } from '../utils/enums'; +import { Fn } from '../utils/type'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { time } from 'console'; const MODULE_NAME = 'OPTIMIZELY'; @@ -81,8 +83,8 @@ type StringInputs = Partial<Record<InputKey, unknown>>; export default class Optimizely implements Client { private isOptimizelyConfigValid: boolean; - private disposeOnUpdate: (() => void) | null; - private readyPromise: Promise<{ success: boolean; reason?: string }>; + private disposeOnUpdate?: Fn; + private readyPromise: Promise<unknown>; // readyTimeout is specified as any to make this work in both browser & Node // eslint-disable-next-line @typescript-eslint/no-explicit-any private readyTimeouts: { [key: string]: { readyTimeout: any; onClose: () => void } }; @@ -128,12 +130,7 @@ export default class Optimizely implements Client { } }); this.defaultDecideOptions = defaultDecideOptions; - this.projectConfigManager = createProjectConfigManager({ - datafile: config.datafile, - jsonSchemaValidator: config.jsonSchemaValidator, - sdkKey: config.sdkKey, - datafileManager: config.datafileManager, - }); + this.projectConfigManager = config.projectConfigManager; this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { this.logger.log( @@ -149,7 +146,8 @@ export default class Optimizely implements Client { this.updateOdpSettings(); }); - const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); + this.projectConfigManager.start(); + const projectConfigManagerRunningPromise = this.projectConfigManager.onRunning(); let userProfileService: UserProfileService | null = null; if (config.userProfileService) { @@ -176,13 +174,10 @@ export default class Optimizely implements Client { const eventProcessorStartedPromise = this.eventProcessor.start(); this.readyPromise = Promise.all([ - projectConfigManagerReadyPromise, + projectConfigManagerRunningPromise, eventProcessorStartedPromise, config.odpManager ? config.odpManager.onReady() : Promise.resolve(), - ]).then(promiseResults => { - // Only return status from project config promise because event processor promise does not return any status. - return promiseResults[0]; - }); + ]); this.readyTimeouts = {}; this.nextReadyTimeoutId = 0; @@ -193,7 +188,7 @@ export default class Optimizely implements Client { * @return {projectConfig.ProjectConfig} */ getProjectConfig(): projectConfig.ProjectConfig | null { - return this.projectConfigManager.getConfig(); + return this.projectConfigManager.getConfig() || null; } /** @@ -1262,7 +1257,7 @@ export default class Optimizely implements Client { if (!configObj) { return null; } - return this.projectConfigManager.getOptimizelyConfig(); + return this.projectConfigManager.getOptimizelyConfig() || null; } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); @@ -1308,15 +1303,11 @@ export default class Optimizely implements Client { } this.notificationCenter.clearAllNotificationListeners(); - const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; - if (sdkKey) { - NotificationRegistry.removeNotificationCenter(sdkKey); - } const eventProcessorStoppedPromise = this.eventProcessor.stop(); if (this.disposeOnUpdate) { this.disposeOnUpdate(); - this.disposeOnUpdate = null; + this.disposeOnUpdate = undefined; } if (this.projectConfigManager) { this.projectConfigManager.stop(); @@ -1377,7 +1368,7 @@ export default class Optimizely implements Client { * @param {number|undefined} options.timeout * @return {Promise} */ - onReady(options?: { timeout?: number }): Promise<OnReadyResult> { + onReady(options?: { timeout?: number }): Promise<unknown> { let timeoutValue: number | undefined; if (typeof options === 'object' && options !== null) { if (options.timeout !== undefined) { @@ -1388,27 +1379,20 @@ export default class Optimizely implements Client { timeoutValue = DEFAULT_ONREADY_TIMEOUT; } - let resolveTimeoutPromise: (value: OnReadyResult) => void; - const timeoutPromise = new Promise<OnReadyResult>(resolve => { - resolveTimeoutPromise = resolve; - }); + const timeoutPromise = resolvablePromise(); - const timeoutId = this.nextReadyTimeoutId; - this.nextReadyTimeoutId++; + const timeoutId = this.nextReadyTimeoutId++; const onReadyTimeout = () => { delete this.readyTimeouts[timeoutId]; - resolveTimeoutPromise({ - success: false, - reason: sprintf('onReady timeout expired after %s ms', timeoutValue), - }); + timeoutPromise.reject(new Error( + sprintf('onReady timeout expired after %s ms', timeoutValue) + )); }; + const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); const onClose = function() { - resolveTimeoutPromise({ - success: false, - reason: 'Instance closed', - }); + timeoutPromise.reject(new Error('Instance closed')); }; this.readyTimeouts[timeoutId] = { @@ -1419,9 +1403,6 @@ export default class Optimizely implements Client { this.readyPromise.then(() => { clearTimeout(readyTimeout); delete this.readyTimeouts[timeoutId]; - resolveTimeoutPromise({ - success: true, - }); }); return Promise.race([this.readyPromise, timeoutPromise]); diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 2b6ce0653..8c436391c 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -1,18 +1,19 @@ -/**************************************************************************** - * Copyright 2020-2023, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2020-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { assert } from 'chai'; import sinon from 'sinon'; @@ -30,6 +31,8 @@ import eventDispatcher from '../plugins/event_dispatcher/index.node'; import { CONTROL_ATTRIBUTES, LOG_LEVEL, LOG_MESSAGES } from '../utils/enums'; import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import { createProjectConfig } from '../project_config/project_config'; describe('lib/optimizely_user_context', function() { describe('APIs', function() { @@ -356,7 +359,9 @@ describe('lib/optimizely_user_context', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), errorHandler: errorHandler, eventProcessor, isValidInstance: true, @@ -689,7 +694,9 @@ describe('lib/optimizely_user_context', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), errorHandler: errorHandler, eventProcessor, isValidInstance: true, @@ -791,7 +798,9 @@ describe('lib/optimizely_user_context', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), errorHandler: errorHandler, eventProcessor, isValidInstance: true, @@ -833,7 +842,9 @@ describe('lib/optimizely_user_context', function() { }); var optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), errorHandler: errorHandler, eventProcessor, isValidInstance: true, diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index 0b689237a..92b307dbb 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2020-2024, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2020-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import Optimizely from '../optimizely'; import { EventTags, @@ -64,11 +64,9 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { this.forcedDecisionsMap = {}; if (shouldIdentifyUser) { - this.optimizely.onReady().then(({ success }) => { - if (success) { - this.identifyUser(); - } - }); + this.optimizely.onReady().then(() => { + this.identifyUser(); + }).catch(() => {}); } } diff --git a/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts b/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts deleted file mode 100644 index 9bc89aa53..000000000 --- a/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2021-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import { LoggerFacade } from '../../modules/logging'; - import { HttpPollingDatafileManager } from '../../modules/datafile-manager/index.browser'; - import { DatafileOptions, DatafileManagerConfig, DatafileManager } from '../../shared_types'; - import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; - import fns from '../../utils/fns'; - - export function createHttpPollingDatafileManager( - sdkKey: string, - logger: LoggerFacade, - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - datafileOptions?: DatafileOptions, - ): DatafileManager { - const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; - if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { - fns.assign(datafileManagerConfig, datafileOptions); - } - if (datafile) { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: datafile, - jsonSchemaValidator: undefined, - logger: logger, - }); - - if (error) { - logger.error(error); - } - if (configObj) { - datafileManagerConfig.datafile = toDatafile(configObj); - } - } - return new HttpPollingDatafileManager(datafileManagerConfig); - } diff --git a/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js b/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js deleted file mode 100644 index ffd9b369a..000000000 --- a/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright 2021 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { createHttpPollingDatafileManager } from './http_polling_datafile_manager'; -import * as projectConfig from '../../core/project_config'; -import datafileManager from '../../modules/datafile-manager/index.node'; - -describe('lib/plugins/datafile_manager/http_polling_datafile_manager', function() { - var sandbox = sinon.sandbox.create(); - - beforeEach(() => { - sandbox.stub(datafileManager,'HttpPollingDatafileManager') - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('when datafile is null', () => { - beforeEach(() => { - sandbox.stub(projectConfig, 'toDatafile'); - sandbox.stub(projectConfig, 'tryCreatingProjectConfig'); - }); - it('should create HttpPollingDatafileManager with correct options and not create project config', () => { - var logger = { - error: () => {}, - } - createHttpPollingDatafileManager('SDK_KEY', logger, undefined, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/' - }); - - sinon.assert.calledWithExactly(datafileManager.HttpPollingDatafileManager, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/', - sdkKey: 'SDK_KEY', - }); - sinon.assert.notCalled(projectConfig.tryCreatingProjectConfig); - sinon.assert.notCalled(projectConfig.toDatafile); - }); - }); - - describe('when initial datafile is provided', () => { - beforeEach(() => { - sandbox.stub(projectConfig, 'tryCreatingProjectConfig').returns({ configObj: { dummy: "Config" }, error: null}); - sandbox.stub(projectConfig, 'toDatafile').returns('{"dummy": "datafile"}'); - }); - it('should create project config and add datafile', () => { - var logger = { - error: () => {}, - } - var dummyDatafile = '{"dummy": "datafile"}'; - createHttpPollingDatafileManager('SDK_KEY', logger, dummyDatafile, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/' - }); - - sinon.assert.calledWithExactly(datafileManager.HttpPollingDatafileManager, { - datafile: dummyDatafile, - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/', - sdkKey: 'SDK_KEY', - }); - sinon.assert.calledWithExactly(projectConfig.tryCreatingProjectConfig, { - datafile: dummyDatafile, - jsonSchemaValidator: undefined, - logger, - }); - sinon.assert.calledWithExactly(projectConfig.toDatafile, {dummy: "Config"}); - }) - }) - - describe('error logging', () => { - beforeEach(() => { - sandbox.stub(projectConfig, 'tryCreatingProjectConfig').returns({ configObj: null, error: 'Error creating config'}); - sandbox.stub(projectConfig, 'toDatafile'); - }); - it('Should log error when error is thrown while creating project config', () => { - var logger = { - error: () => {}, - } - var errorSpy = sandbox.spy(logger, 'error'); - var dummyDatafile = '{"dummy": "datafile"}'; - createHttpPollingDatafileManager('SDK_KEY', logger, dummyDatafile, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/' - }); - - sinon.assert.calledWithExactly(datafileManager.HttpPollingDatafileManager, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/', - sdkKey: 'SDK_KEY', - }); - sinon.assert.calledWithExactly(projectConfig.tryCreatingProjectConfig, { - datafile: dummyDatafile, - jsonSchemaValidator: undefined, - logger, - }); - sinon.assert.notCalled(projectConfig.toDatafile); - sinon.assert.calledWithExactly(errorSpy, 'Error creating config'); - }) - }); -}); diff --git a/lib/plugins/datafile_manager/http_polling_datafile_manager.ts b/lib/plugins/datafile_manager/http_polling_datafile_manager.ts deleted file mode 100644 index 7bbd19738..000000000 --- a/lib/plugins/datafile_manager/http_polling_datafile_manager.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2021-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerFacade } from '../../modules/logging'; -import datafileManager from '../../modules/datafile-manager/index.node'; -import { DatafileOptions, DatafileManagerConfig, DatafileManager } from '../../shared_types'; -import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; -import fns from '../../utils/fns'; - -export function createHttpPollingDatafileManager( - sdkKey: string, - logger: LoggerFacade, - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - datafileOptions?: DatafileOptions, -): DatafileManager { - const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; - if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { - fns.assign(datafileManagerConfig, datafileOptions); - } - if (datafile) { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: datafile, - jsonSchemaValidator: undefined, - logger: logger, - }); - - if (error) { - logger.error(error); - } - if (configObj) { - datafileManagerConfig.datafile = toDatafile(configObj); - } - } - return new datafileManager.HttpPollingDatafileManager(datafileManagerConfig); -} diff --git a/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js b/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js deleted file mode 100644 index 1eacb169b..000000000 --- a/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2021 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import { assert } from 'chai'; - import { createNoOpDatafileManager } from './no_op_datafile_manager'; - - describe('lib/plugins/datafile_manager/no_op_datafile_manager', function() { - var dfm = createNoOpDatafileManager(); - - beforeEach(() => { - dfm.start(); - }); - - it('should return empty string when get is called', () => { - assert.equal(dfm.get(), ''); - }); - - it('should return a resolved promise when onReady is called', (done) => { - dfm.onReady().then(done); - }); - - it('should return a resolved promise when stop is called', (done) => { - dfm.stop().then(done); - }); - - it('should return an empty function when event listener is added', () => { - assert.equal(typeof(dfm.on('dummyEvent', () => {}, '')), 'function'); - }); - }); - \ No newline at end of file diff --git a/lib/plugins/datafile_manager/no_op_datafile_manager.ts b/lib/plugins/datafile_manager/no_op_datafile_manager.ts deleted file mode 100644 index 2f1926d4f..000000000 --- a/lib/plugins/datafile_manager/no_op_datafile_manager.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2021, 2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { DatafileManager, DatafileUpdateListener } from '../../shared_types'; - -/** - * No-operation Datafile Manager for Lite Bundle designed for Edge platforms - * https://github.com/optimizely/javascript-sdk/issues/699 - */ -class NoOpDatafileManager implements DatafileManager { - /* eslint-disable @typescript-eslint/no-unused-vars */ - on(_eventName: string, _listener: DatafileUpdateListener): () => void { - return (): void => {}; - } - - get(): string { - return ''; - } - - onReady(): Promise<void> { - return Promise.resolve(); - } - - start(): void {} - - stop(): Promise<void> { - return Promise.resolve(); - } -} - -export function createNoOpDatafileManager(): DatafileManager { - return new NoOpDatafileManager(); -} diff --git a/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts b/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts deleted file mode 100644 index 0d45d2116..000000000 --- a/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2021-2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerFacade } from '../../modules/logging'; -import { HttpPollingDatafileManager } from '../../modules/datafile-manager/index.react_native'; -import { DatafileOptions, DatafileManager, PersistentCacheProvider } from '../../shared_types'; -import { DatafileManagerConfig } from '../../modules/datafile-manager/index.react_native'; -import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; -import fns from '../../utils/fns'; - -export function createHttpPollingDatafileManager( - sdkKey: string, - logger: LoggerFacade, - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - datafileOptions?: DatafileOptions, - persistentCacheProvider?: PersistentCacheProvider, - ): DatafileManager { - const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; - if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { - fns.assign(datafileManagerConfig, datafileOptions); - } - if (datafile) { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: datafile, - jsonSchemaValidator: undefined, - logger: logger, - }); - - if (error) { - logger.error(error); - } - if (configObj) { - datafileManagerConfig.datafile = toDatafile(configObj); - } - } - if (persistentCacheProvider) { - datafileManagerConfig.cache = persistentCacheProvider(); - } - return new HttpPollingDatafileManager(datafileManagerConfig); -} diff --git a/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/plugins/odp/event_api_manager/index.browser.ts index 8a21a462c..e8feb29ee 100644 --- a/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/lib/plugins/odp/event_api_manager/index.browser.ts @@ -1,23 +1,24 @@ -/**************************************************************************** - * Copyright 2024, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; +import { HttpMethod } from '../../../utils/http_request_handler/http'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; @@ -41,7 +42,7 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { protected generateRequestData( odpConfig: OdpConfig, events: OdpEvent[] - ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { + ): { method: HttpMethod; endpoint: string; headers: { [key: string]: string }; data: string } { const pixelApiEndpoint = this.getPixelApiEndpoint(odpConfig); const apiKey = odpConfig.apiKey; diff --git a/lib/plugins/odp/event_api_manager/index.node.ts b/lib/plugins/odp/event_api_manager/index.node.ts index 0b8b4e3ba..eea898787 100644 --- a/lib/plugins/odp/event_api_manager/index.node.ts +++ b/lib/plugins/odp/event_api_manager/index.node.ts @@ -1,23 +1,24 @@ -/**************************************************************************** - * Copyright 2024, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; +import { HttpMethod } from '../../../utils/http_request_handler/http'; export class NodeOdpEventApiManager extends OdpEventApiManager { protected shouldSendEvents(events: OdpEvent[]): boolean { return true; @@ -26,7 +27,7 @@ export class NodeOdpEventApiManager extends OdpEventApiManager { protected generateRequestData( odpConfig: OdpConfig, events: OdpEvent[] - ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { + ): { method: HttpMethod; endpoint: string; headers: { [key: string]: string }; data: string } { const { apiHost, apiKey } = odpConfig; diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index e7095364a..5001dc59f 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -83,10 +83,10 @@ export class BrowserOdpManager extends OdpManager { if (odpOptions?.segmentsRequestHandler) { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; } else { - customSegmentRequestHandler = new BrowserRequestHandler( + customSegmentRequestHandler = new BrowserRequestHandler({ logger, - odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS - ); + timeout: odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS + }); } let segmentManager: IOdpSegmentManager; @@ -111,10 +111,10 @@ export class BrowserOdpManager extends OdpManager { if (odpOptions?.eventRequestHandler) { customEventRequestHandler = odpOptions.eventRequestHandler; } else { - customEventRequestHandler = new BrowserRequestHandler( + customEventRequestHandler = new BrowserRequestHandler({ logger, - odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS - ); + timeout:odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS + }); } let eventManager: IOdpEventManager; diff --git a/lib/plugins/odp_manager/index.node.ts b/lib/plugins/odp_manager/index.node.ts index bdd57f1ad..9eebc71d1 100644 --- a/lib/plugins/odp_manager/index.node.ts +++ b/lib/plugins/odp_manager/index.node.ts @@ -74,10 +74,10 @@ export class NodeOdpManager extends OdpManager { if (odpOptions?.segmentsRequestHandler) { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; } else { - customSegmentRequestHandler = new NodeRequestHandler( + customSegmentRequestHandler = new NodeRequestHandler({ logger, - odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS - ); + timeout: odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS + }); } let segmentManager: IOdpSegmentManager; @@ -102,10 +102,10 @@ export class NodeOdpManager extends OdpManager { if (odpOptions?.eventRequestHandler) { customEventRequestHandler = odpOptions.eventRequestHandler; } else { - customEventRequestHandler = new NodeRequestHandler( + customEventRequestHandler = new NodeRequestHandler({ logger, - odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS - ); + timeout: odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS + }); } let eventManager: IOdpEventManager; diff --git a/lib/project_config/config_manager_factory.browser.spec.ts b/lib/project_config/config_manager_factory.browser.spec.ts new file mode 100644 index 000000000..bbabfb0ac --- /dev/null +++ b/lib/project_config/config_manager_factory.browser.spec.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./config_manager_factory', () => { + return { + getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + }; +}); + +vi.mock('../utils/http_request_handler/browser_request_handler', () => { + const BrowserRequestHandler = vi.fn(); + return { BrowserRequestHandler }; +}); + +import { getPollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; +import { createPollingProjectConfigManager } from './config_manager_factory.browser'; +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; + +describe('createPollingConfigManager', () => { + const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + + beforeEach(() => { + mockGetPollingConfigManager.mockClear(); + MockBrowserRequestHandler.mockClear(); + }); + + it('creates and returns the instance by calling getPollingConfigManager', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(projectConfigManager, mockGetPollingConfigManager.mock.results[0].value)).toBe(true); + }); + + it('uses an instance of BrowserRequestHandler as requestHandler', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0])).toBe(true); + }); + + it('uses uses autoUpdate = false by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(false); + }); + + it('uses the provided options', () => { + const config: PollingConfigManagerConfig = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + updateInterval: 50000, + autoUpdate: true, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + }); +}); diff --git a/lib/project_config/config_manager_factory.browser.ts b/lib/project_config/config_manager_factory.browser.ts new file mode 100644 index 000000000..8ae0bfd9e --- /dev/null +++ b/lib/project_config/config_manager_factory.browser.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { ProjectConfigManager } from './project_config_manager'; + +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { + const defaultConfig = { + autoUpdate: false, + requestHandler: new BrowserRequestHandler(), + }; + return getPollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/lib/project_config/config_manager_factory.node.spec.ts b/lib/project_config/config_manager_factory.node.spec.ts new file mode 100644 index 000000000..2667e5cf5 --- /dev/null +++ b/lib/project_config/config_manager_factory.node.spec.ts @@ -0,0 +1,86 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./config_manager_factory', () => { + return { + getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + }; +}); + +vi.mock('../utils/http_request_handler/node_request_handler', () => { + const NodeRequestHandler = vi.fn(); + return { NodeRequestHandler }; +}); + +import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { createPollingProjectConfigManager } from './config_manager_factory.node'; +import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE } from './constant'; + +describe('createPollingConfigManager', () => { + const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); + const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); + + beforeEach(() => { + mockGetPollingConfigManager.mockClear(); + MockNodeRequestHandler.mockClear(); + }); + + it('creates and returns the instance by calling getPollingConfigManager', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(projectConfigManager, mockGetPollingConfigManager.mock.results[0].value)).toBe(true); + }); + + it('uses an instance of NodeRequestHandler as requestHandler', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].requestHandler, MockNodeRequestHandler.mock.instances[0])).toBe(true); + }); + + it('uses uses autoUpdate = true by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); + }); + + it('uses the provided options', () => { + const config: PollingConfigManagerConfig = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + updateInterval: 50000, + autoUpdate: false, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + }); +}); diff --git a/lib/project_config/config_manager_factory.node.ts b/lib/project_config/config_manager_factory.node.ts new file mode 100644 index 000000000..7a220bc12 --- /dev/null +++ b/lib/project_config/config_manager_factory.node.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { NodeRequestHandler } from "../utils/http_request_handler/node_request_handler"; +import { ProjectConfigManager } from "./project_config_manager"; +import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './constant'; + +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { + const defaultConfig = { + autoUpdate: true, + requestHandler: new NodeRequestHandler(), + }; + return getPollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts new file mode 100644 index 000000000..a01b36c11 --- /dev/null +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -0,0 +1,102 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./config_manager_factory', () => { + return { + getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + }; +}); + +vi.mock('../utils/http_request_handler/browser_request_handler', () => { + const BrowserRequestHandler = vi.fn(); + return { BrowserRequestHandler }; +}); + +vi.mock('../plugins/key_value_cache/reactNativeAsyncStorageCache', () => { + const ReactNativeAsyncStorageCache = vi.fn(); + return { 'default': ReactNativeAsyncStorageCache }; +}); + +import { getPollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; +import { createPollingProjectConfigManager } from './config_manager_factory.react_native'; +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import ReactNativeAsyncStorageCache from '../plugins/key_value_cache/reactNativeAsyncStorageCache'; + +describe('createPollingConfigManager', () => { + const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const MockReactNativeAsyncStorageCache = vi.mocked(ReactNativeAsyncStorageCache); + + beforeEach(() => { + mockGetPollingConfigManager.mockClear(); + MockBrowserRequestHandler.mockClear(); + MockReactNativeAsyncStorageCache.mockClear(); + }); + + it('creates and returns the instance by calling getPollingConfigManager', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(projectConfigManager, mockGetPollingConfigManager.mock.results[0].value)).toBe(true); + }); + + it('uses an instance of BrowserRequestHandler as requestHandler', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0])).toBe(true); + }); + + it('uses uses autoUpdate = true by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); + }); + + it('uses an instance of ReactNativeAsyncStorageCache for caching by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockReactNativeAsyncStorageCache.mock.instances[0])).toBe(true); + }); + + it('uses the provided options', () => { + const config: PollingConfigManagerConfig = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + updateInterval: 50000, + autoUpdate: false, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + }); +}); diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts new file mode 100644 index 000000000..6978ac61e --- /dev/null +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; +import { ProjectConfigManager } from "./project_config_manager"; +import ReactNativeAsyncStorageCache from "../plugins/key_value_cache/reactNativeAsyncStorageCache"; + +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { + const defaultConfig = { + autoUpdate: true, + requestHandler: new BrowserRequestHandler(), + cache: new ReactNativeAsyncStorageCache(), + }; + return getPollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/lib/project_config/config_manager_factory.spec.ts b/lib/project_config/config_manager_factory.spec.ts new file mode 100644 index 000000000..a79b3ae1a --- /dev/null +++ b/lib/project_config/config_manager_factory.spec.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./project_config_manager', () => { + const MockConfigManager = vi.fn(); + return { ProjectConfigManagerImpl: MockConfigManager }; +}); + +vi.mock('./polling_datafile_manager', () => { + const MockDatafileManager = vi.fn(); + return { PollingDatafileManager: MockDatafileManager }; +}); + +vi.mock('../utils/repeater/repeater', () => { + const MockIntervalRepeater = vi.fn(); + const MockExponentialBackoff = vi.fn(); + return { IntervalRepeater: MockIntervalRepeater, ExponentialBackoff: MockExponentialBackoff }; +}); + +import { ProjectConfigManagerImpl } from './project_config_manager'; +import { PollingDatafileManager } from './polling_datafile_manager'; +import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; +import { getPollingConfigManager } from './config_manager_factory'; +import { DEFAULT_UPDATE_INTERVAL } from './constant'; + +describe('getPollingConfigManager', () => { + const MockProjectConfigManagerImpl = vi.mocked(ProjectConfigManagerImpl); + const MockPollingDatafileManager = vi.mocked(PollingDatafileManager); + const MockIntervalRepeater = vi.mocked(IntervalRepeater); + const MockExponentialBackoff = vi.mocked(ExponentialBackoff); + + beforeEach(() => { + MockProjectConfigManagerImpl.mockClear(); + MockPollingDatafileManager.mockClear(); + MockIntervalRepeater.mockClear(); + MockExponentialBackoff.mockClear(); + }); + + it('uses a repeater with exponential backoff for the datafileManager', () => { + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + }; + const projectConfigManager = getPollingConfigManager(config); + expect(Object.is(projectConfigManager, MockProjectConfigManagerImpl.mock.instances[0])).toBe(true); + const usedDatafileManager = MockProjectConfigManagerImpl.mock.calls[0][0].datafileManager; + expect(Object.is(usedDatafileManager, MockPollingDatafileManager.mock.instances[0])).toBe(true); + const usedRepeater = MockPollingDatafileManager.mock.calls[0][0].repeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + const usedBackoff = MockIntervalRepeater.mock.calls[0][1]; + expect(Object.is(usedBackoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + }); + + it('uses the default update interval if not provided', () => { + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + }; + getPollingConfigManager(config); + expect(MockIntervalRepeater.mock.calls[0][0]).toBe(DEFAULT_UPDATE_INTERVAL); + expect(MockPollingDatafileManager.mock.calls[0][0].updateInterval).toBe(DEFAULT_UPDATE_INTERVAL); + }); + + it('uses the provided options', () => { + const config = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + updateInterval: 50000, + autoUpdate: true, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + }; + + getPollingConfigManager(config); + expect(MockIntervalRepeater.mock.calls[0][0]).toBe(config.updateInterval); + expect(MockExponentialBackoff).toHaveBeenNthCalledWith(1, 1000, config.updateInterval, 500); + + expect(MockPollingDatafileManager).toHaveBeenNthCalledWith(1, expect.objectContaining({ + sdkKey: config.sdkKey, + autoUpdate: config.autoUpdate, + updateInterval: config.updateInterval, + urlTemplate: config.urlTemplate, + datafileAccessToken: config.datafileAccessToken, + requestHandler: config.requestHandler, + repeater: MockIntervalRepeater.mock.instances[0], + cache: config.cache, + })); + + expect(MockProjectConfigManagerImpl).toHaveBeenNthCalledWith(1, expect.objectContaining({ + datafile: config.datafile, + jsonSchemaValidator: config.jsonSchemaValidator, + })); + }); +}); diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts new file mode 100644 index 000000000..4d1977663 --- /dev/null +++ b/lib/project_config/config_manager_factory.ts @@ -0,0 +1,76 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from "../utils/http_request_handler/http"; +import { Transformer } from "../utils/type"; +import { DatafileManagerConfig } from "./datafile_manager"; +import { ProjectConfigManagerImpl, ProjectConfigManager } from "./project_config_manager"; +import { PollingDatafileManager } from "./polling_datafile_manager"; +import PersistentKeyValueCache from "../plugins/key_value_cache/persistentKeyValueCache"; +import { DEFAULT_UPDATE_INTERVAL } from './constant'; +import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; + +export type StaticConfigManagerConfig = { + datafile: string, + jsonSchemaValidator?: Transformer<unknown, boolean>, +}; + +export const createStaticProjectConfigManager = ( + config: StaticConfigManagerConfig +): ProjectConfigManager => { + return new ProjectConfigManagerImpl(config); +}; + +export type PollingConfigManagerConfig = { + datafile?: string, + sdkKey: string, + jsonSchemaValidator?: Transformer<unknown, boolean>, + autoUpdate?: boolean; + updateInterval?: number; + urlTemplate?: string; + datafileAccessToken?: string; + cache?: PersistentKeyValueCache; +}; + +export type PollingConfigManagerFactoryOptions = PollingConfigManagerConfig & { requestHandler: RequestHandler }; + +export const getPollingConfigManager = ( + opt: PollingConfigManagerFactoryOptions +): ProjectConfigManager => { + const updateInterval = opt.updateInterval ?? DEFAULT_UPDATE_INTERVAL; + + const backoff = new ExponentialBackoff(1000, updateInterval, 500); + const repeater = new IntervalRepeater(updateInterval, backoff); + + const datafileManagerConfig: DatafileManagerConfig = { + sdkKey: opt.sdkKey, + autoUpdate: opt.autoUpdate, + updateInterval: updateInterval, + urlTemplate: opt.urlTemplate, + datafileAccessToken: opt.datafileAccessToken, + requestHandler: opt.requestHandler, + cache: opt.cache, + repeater, + }; + + const datafileManager = new PollingDatafileManager(datafileManagerConfig); + + return new ProjectConfigManagerImpl({ + datafile: opt.datafile, + datafileManager, + jsonSchemaValidator: opt.jsonSchemaValidator, + }); +}; diff --git a/lib/modules/datafile-manager/config.ts b/lib/project_config/constant.ts similarity index 100% rename from lib/modules/datafile-manager/config.ts rename to lib/project_config/constant.ts diff --git a/lib/modules/datafile-manager/datafileManager.ts b/lib/project_config/datafile_manager.ts similarity index 55% rename from lib/modules/datafile-manager/datafileManager.ts rename to lib/project_config/datafile_manager.ts index abf11d8e9..32798495e 100644 --- a/lib/modules/datafile-manager/datafileManager.ts +++ b/lib/project_config/datafile_manager.ts @@ -13,39 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import PersistentKeyValueCache from '../../plugins/key_value_cache/persistentKeyValueCache'; +import { Service } from '../service'; +import PersistentKeyValueCache from '../plugins/key_value_cache/persistentKeyValueCache'; +import { RequestHandler } from '../utils/http_request_handler/http'; +import { Fn, Consumer } from '../utils/type'; +import { Repeater } from '../utils/repeater/repeater'; +import { LoggerFacade } from '../modules/logging'; -export interface DatafileUpdate { - datafile: string; +export interface DatafileManager extends Service { + get(): string | undefined; + onUpdate(listener: Consumer<string>): Fn; + setLogger(logger: LoggerFacade): void; } -export interface DatafileUpdateListener { - (datafileUpdate: DatafileUpdate): void; -} - -// TODO: Replace this with the one from js-sdk-models -interface Managed { - start(): void; - - stop(): Promise<any>; -} - -export interface DatafileManager extends Managed { - get: () => string; - on: (eventName: string, listener: DatafileUpdateListener) => () => void; - onReady: () => Promise<void>; -} - -export interface DatafileManagerConfig { +export type DatafileManagerConfig = { + requestHandler: RequestHandler; autoUpdate?: boolean; - datafile?: string; sdkKey: string; /** Polling interval in milliseconds to check for datafile updates. */ updateInterval?: number; urlTemplate?: string; cache?: PersistentKeyValueCache; -} - -export interface NodeDatafileManagerConfig extends DatafileManagerConfig { datafileAccessToken?: string; + initRetry?: number; + repeater: Repeater; + logger?: LoggerFacade; } diff --git a/lib/project_config/polling_datafile_manager.spec.ts b/lib/project_config/polling_datafile_manager.spec.ts new file mode 100644 index 000000000..8e12ac3f5 --- /dev/null +++ b/lib/project_config/polling_datafile_manager.spec.ts @@ -0,0 +1,951 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; + +import { PollingDatafileManager} from './polling_datafile_manager'; +import { getMockRepeater } from '../tests/mock/mock_repeater'; +import { getMockAbortableRequest, getMockRequestHandler } from '../tests/mock/mock_request_handler'; +import PersistentKeyValueCache from '../../lib/plugins/key_value_cache/persistentKeyValueCache'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE, MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { ServiceState } from '../service'; +import exp from 'constants'; + +const testCache = (): PersistentKeyValueCache => ({ + get(key: string): Promise<string | undefined> { + let val = undefined; + switch (key) { + case 'opt-datafile-keyThatExists': + val = JSON.stringify({ name: 'keyThatExists' }); + break; + } + return Promise.resolve(val); + }, + + set(): Promise<void> { + return Promise.resolve(); + }, + + contains(): Promise<boolean> { + return Promise.resolve(false); + }, + + remove(): Promise<boolean> { + return Promise.resolve(false); + }, +}); + +describe('PollingDatafileManager', () => { + it('should log polling interval below MIN_UPDATE_INTERVAL', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const logger = getMockLogger(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + logger, + updateInterval: MIN_UPDATE_INTERVAL - 1000, + }); + manager.start(); + expect(logger.warn).toHaveBeenCalledWith(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE); + }); + + it('should not log polling interval above MIN_UPDATE_INTERVAL', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const logger = getMockLogger(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + logger, + updateInterval: MIN_UPDATE_INTERVAL + 1000, + }); + manager.start(); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + it('starts the repeater with immediateExecution on start', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + }); + manager.start(); + expect(repeater.start).toHaveBeenCalledWith(true); + }); + + describe('when cached datafile is available', () => { + it('uses cached version of datafile, resolves onRunning() and calls onUpdate handlers while datafile fetch request waits', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); // response promise is pending + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache: testCache(), + }); + + manager.start(); + repeater.execute(0); + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(listener).toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' })); + }); + + it('uses cached version of datafile, resolves onRunning() and calls onUpdate handlers even if fetch request fails', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache: testCache(), + }); + + manager.start(); + repeater.execute(0); + + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' })); + }); + + it('uses cached version of datafile, then calls onUpdate when fetch request succeeds after the cache read', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache: testCache(), + }); + + manager.start(); + repeater.execute(0); + + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenNthCalledWith(1, JSON.stringify({ name: 'keyThatExists' })); + + mockResponse.mockResponse.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} }); + await mockResponse.mockResponse; + expect(listener).toHaveBeenNthCalledWith(2, '{"foo": "bar"}'); + }); + + it('ignores cached datafile if fetch request succeeds before cache read completes', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const cache = testCache(); + // this will be resolved after the requestHandler response is resolved + const cachePromise = resolvablePromise<string | undefined>(); + cache.get = () => cachePromise.promise; + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache, + }); + + manager.start(); + repeater.execute(0); + + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith('{"foo": "bar"}'); + + cachePromise.resolve(JSON.stringify({ name: 'keyThatExists '})); + await cachePromise.promise; + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).not.toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' })); + }); + }); + + it('returns a failing promise to repeater if requestHandler.makeRequest return non-success status code', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 500, body: '', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + }); + + it('returns a failing promise to repeater if requestHandler.makeRequest promise fails', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + }); + + it('returns a promise that resolves to repeater if requestHandler.makeRequest succeedes', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + const ret = repeater.execute(0); + await expect(ret).resolves.not.toThrow(); + }); + + describe('start', () => { + it('retries specified number of times before rejecting onRunning() and onTerminated() when autoupdate is true', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 5, + autoUpdate: true, + }); + + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(6); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('retries specified number of times before rejecting onRunning() and onTerminated() when autoupdate is false', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 5, + autoUpdate: false, + }); + + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(6); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('stops the repeater when initalization fails', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 0, + autoUpdate: false, + }); + + manager.start(); + repeater.execute(0); + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(1); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + }); + + it('retries specified number of times before rejecting onRunning() and onTerminated() when provided cache does not contain datafile', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + initRetry: 5, + cache: testCache(), + }); + + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(6); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('retries init indefinitely if initRetry is not provided when autoupdate is true', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + const promiseCallback = vi.fn(); + manager.onRunning().finally(promiseCallback); + + manager.start(); + const testTry = 10_000; + + for(let i = 0; i < testTry; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(testTry); + expect(promiseCallback).not.toHaveBeenCalled(); + }); + + it('retries init indefinitely if initRetry is not provided when autoupdate is false', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: false, + }); + + const promiseCallback = vi.fn(); + manager.onRunning().finally(promiseCallback); + + manager.start(); + const testTry = 10_000; + + for(let i = 0; i < testTry; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(testTry); + expect(promiseCallback).not.toHaveBeenCalled(); + }); + + it('successfully resolves onRunning() and calls onUpdate handlers when fetch request succeeds', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + const listener = vi.fn(); + + manager.onUpdate(listener); + + manager.start(); + repeater.execute(0); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith('{"foo": "bar"}'); + }); + + it('successfully resolves onRunning() and calls onUpdate handlers when fetch request succeeds after retries', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockFailure = getMockAbortableRequest(Promise.reject('test error')); + const mockSuccess = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockFailure) + .mockReturnValueOnce(mockFailure).mockReturnValueOnce(mockSuccess); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 5, + }); + + const listener = vi.fn(); + + manager.onUpdate(listener); + + manager.start(); + for(let i = 0; i < 2; i++) { + const ret = repeater.execute(0); + expect(ret).rejects.toThrow(); + } + + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(3); + expect(listener).toHaveBeenCalledWith('{"foo": "bar"}'); + }); + + it('stops repeater after successful initialization if autoupdate is false', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: false, + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + }); + + it('saves the datafile in cache', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const cache = testCache(); + const spy = vi.spyOn(cache, 'set'); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + cache, + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(spy).toHaveBeenCalledWith('opt-datafile-keyThatDoesNotExists', '{"foo": "bar"}'); + }); + }); + + describe('autoupdate', () => { + it('fetches datafile on each tick and calls onUpdate handlers when fetch request succeeds', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo2": "bar2"}', headers: {} })); + const mockResponse3 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo3": "bar3"}', headers: {} })); + + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + for(let i = 0; i <3; i++) { + const ret = repeater.execute(0); + await expect(ret).resolves.not.toThrow(); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(3); + expect(listener).toHaveBeenNthCalledWith(1, '{"foo": "bar"}'); + expect(listener).toHaveBeenNthCalledWith(2, '{"foo2": "bar2"}'); + expect(listener).toHaveBeenNthCalledWith(3, '{"foo3": "bar3"}'); + }); + + it('saves the datafile each time in cache', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo2": "bar2"}', headers: {} })); + const mockResponse3 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo3": "bar3"}', headers: {} })); + + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const cache = testCache(); + const spy = vi.spyOn(cache, 'set'); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + autoUpdate: true, + cache, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + for(let i = 0; i <3; i++) { + const ret = repeater.execute(0); + await expect(ret).resolves.not.toThrow(); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(spy).toHaveBeenNthCalledWith(1, 'opt-datafile-keyThatDoesNotExists', '{"foo": "bar"}'); + expect(spy).toHaveBeenNthCalledWith(2, 'opt-datafile-keyThatDoesNotExists', '{"foo2": "bar2"}'); + expect(spy).toHaveBeenNthCalledWith(3, 'opt-datafile-keyThatDoesNotExists', '{"foo3": "bar3"}'); + }); + + it('logs an error if fetch request fails and does not call onUpdate handler', async () => { + const logger = getMockLogger(); + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2= getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse2); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + logger, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + for(let i = 0; i < 3; i++) { + await repeater.execute(0).catch(() => {}); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(logger.error).toHaveBeenCalledTimes(2); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('logs an error if fetch returns non success response and does not call onUpdate handler', async () => { + const logger = getMockLogger(); + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2= getMockAbortableRequest(Promise.resolve({ statusCode: 500, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse2); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + logger, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + for(let i = 0; i < 3; i++) { + await repeater.execute(0).catch(() => {}); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(logger.error).toHaveBeenCalledTimes(2); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('saves and uses last-modified header', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: { 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT' } })); + const mockResponse2 = getMockAbortableRequest( + Promise.resolve({ statusCode: 304, body: '', headers: {} })); + const mockResponse3 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo2": "bar2"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + autoUpdate: true, + }); + + manager.start(); + + for(let i = 0; i <3; i++) { + await repeater.execute(0); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + const secondCallHeaders = requestHandler.makeRequest.mock.calls[1][1]; + expect(secondCallHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:17 GMT'); + const thirdCallHeaders = requestHandler.makeRequest.mock.calls[1][1]; + expect(thirdCallHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:17 GMT'); + }); + + it('does not call onUpdate handler if status is 304', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: { 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT' } })); + const mockResponse2 = getMockAbortableRequest( + Promise.resolve({ statusCode: 304, body: '{"foo2": "bar2"}', headers: {} })); + const mockResponse3 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo3": "bar3"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + autoUpdate: true, + }); + + manager.start(); + + const listener = vi.fn(); + manager.onUpdate(listener); + + for(let i = 0; i <3; i++) { + await repeater.execute(0); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(listener).toHaveBeenCalledTimes(2); + expect(listener).not.toHaveBeenCalledWith('{"foo2": "bar2"}'); + expect(listener).toHaveBeenNthCalledWith(1, '{"foo": "bar"}'); + expect(listener).toHaveBeenNthCalledWith(2, '{"foo3": "bar3"}'); + }); + }); + + it('sends the access token in the request Authorization header', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + datafileAccessToken: 'token123', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][1].Authorization).toBe('Bearer token123'); + }); + + it('uses the provided urlTemplate', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + urlTemplate: 'https://example.com/datafile?key=%s', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][0]).toBe('https://example.com/datafile?key=keyThatExists'); + }); + + it('uses the default urlTemplate if none is provided and datafileAccessToken is also not provided', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][0]).toBe(DEFAULT_URL_TEMPLATE.replace('%s', 'keyThatExists')); + }); + + it('uses the default authenticated urlTemplate if none is provided and datafileAccessToken is provided', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + datafileAccessToken: 'token123', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][0]).toBe(DEFAULT_AUTHENTICATED_URL_TEMPLATE.replace('%s', 'keyThatExists')); + }); + + it('returns the datafile from get', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.get()).toBe('{"foo": "bar"}'); + }); + + it('returns undefined from get before becoming ready', () => { + const repeater = getMockRepeater(); + const mockResponse = getMockAbortableRequest(); + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + manager.start(); + expect(manager.get()).toBeUndefined(); + }); + + it('removes the onUpdate handler when the retuned function is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + const listener = vi.fn(); + const removeListener = manager.onUpdate(listener); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(listener).toHaveBeenCalledTimes(1); + removeListener(); + + await repeater.execute(0); + expect(listener).toHaveBeenCalledTimes(1); + }); + + describe('stop', () => { + it('rejects onRunning when stop is called if manager state is New', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + expect(manager.getState()).toBe(ServiceState.New); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('rejects onRunning when stop is called if manager state is Starting', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + manager.start(); + expect(manager.getState()).toBe(ServiceState.Starting); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('stops the repeater, set state to Termimated, and resolve onTerminated when stop is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + manager.start(); + await repeater.execute(0); + await expect(manager.onRunning()).resolves.not.toThrow(); + + manager.stop(); + await expect(manager.onTerminated()).resolves.not.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + expect(manager.getState()).toBe(ServiceState.Terminated); + }); + + it('aborts the current request if stop is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + manager.start(); + repeater.execute(0); + + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + manager.stop(); + expect(mockResponse.abort).toHaveBeenCalled(); + }); + + it('does not call onUpdate handler after stop is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + repeater.execute(0); + manager.stop(); + + expect(listener).not.toHaveBeenCalled(); + }); + }) +}); diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts new file mode 100644 index 000000000..3784fbfd6 --- /dev/null +++ b/lib/project_config/polling_datafile_manager.ts @@ -0,0 +1,245 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerFacade } from '../modules/logging'; +import { sprintf } from '../utils/fns'; +import { DatafileManager, DatafileManagerConfig } from './datafile_manager'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; +import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE, MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; +import PersistentKeyValueCache from '../plugins/key_value_cache/persistentKeyValueCache'; + +import { BaseService, ServiceState } from '../service'; +import { RequestHandler, AbortableRequest, Headers, Response } from '../utils/http_request_handler/http'; +import { Repeater } from '../utils/repeater/repeater'; +import { Consumer, Fn } from '../utils/type'; +import { url } from 'inspector'; + +function isSuccessStatusCode(statusCode: number): boolean { + return statusCode >= 200 && statusCode < 400; +} + +export class PollingDatafileManager extends BaseService implements DatafileManager { + private requestHandler: RequestHandler; + private currentDatafile?: string; + private emitter: EventEmitter<{ update: string }>; + private autoUpdate: boolean; + private initRetryRemaining?: number; + private repeater: Repeater; + private updateInterval?: number; + + private lastResponseLastModified?: string; + private datafileUrl: string; + private currentRequest?: AbortableRequest; + private cacheKey: string; + private cache?: PersistentKeyValueCache; + private sdkKey: string; + private datafileAccessToken?: string; + private logger?: LoggerFacade; + + constructor(config: DatafileManagerConfig) { + super(); + const { + autoUpdate = false, + sdkKey, + datafileAccessToken, + urlTemplate, + cache, + initRetry, + repeater, + requestHandler, + updateInterval, + logger, + } = config; + this.cache = cache; + this.cacheKey = 'opt-datafile-' + sdkKey; + this.sdkKey = sdkKey; + this.datafileAccessToken = datafileAccessToken; + this.requestHandler = requestHandler; + this.emitter = new EventEmitter(); + this.autoUpdate = autoUpdate; + this.initRetryRemaining = initRetry; + this.repeater = repeater; + this.updateInterval = updateInterval; + this.logger = logger; + + const urlTemplateToUse = urlTemplate || + (datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE); + this.datafileUrl = sprintf(urlTemplateToUse, this.sdkKey); + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + onUpdate(listener: Consumer<string>): Fn { + return this.emitter.on('update', listener); + } + + get(): string | undefined { + return this.currentDatafile; + } + + start(): void { + if (!this.isNew()) { + return; + } + + if (this.updateInterval !== undefined && this.updateInterval < MIN_UPDATE_INTERVAL) { + this.logger?.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE); + } + + this.state = ServiceState.Starting; + this.setDatafileFromCacheIfAvailable(); + this.repeater.setTask(this.syncDatafile.bind(this)); + this.repeater.start(true); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew() || this.isStarting()) { + // TOOD: replace message with imported constants + this.startPromise.reject(new Error('Datafile manager stopped before it could be started')); + } + + this.logger?.debug('Datafile manager stopped'); + this.state = ServiceState.Terminated; + this.repeater.stop(); + this.currentRequest?.abort(); + this.emitter.removeAllListeners(); + this.stopPromise.resolve(); + } + + private handleInitFailure(): void { + this.state = ServiceState.Failed; + this.repeater.stop(); + // TODO: replace message with imported constants + const error = new Error('Failed to fetch datafile'); + this.startPromise.reject(error); + this.stopPromise.reject(error); + } + + private handleError(errorOrStatus: Error | number): void { + if (this.isDone()) { + return; + } + + // TODO: replace message with imported constants + if (errorOrStatus instanceof Error) { + this.logger?.error('Error fetching datafile: %s', errorOrStatus.message, errorOrStatus); + } else { + this.logger?.error(`Datafile fetch request failed with status: ${errorOrStatus}`); + } + + if(this.isStarting() && this.initRetryRemaining !== undefined) { + if (this.initRetryRemaining === 0) { + this.handleInitFailure(); + } else { + this.initRetryRemaining--; + } + } + } + + private async onRequestRejected(err: any): Promise<void> { + this.handleError(err); + return Promise.reject(err); + } + + private async onRequestResolved(response: Response): Promise<void> { + if (this.isDone()) { + return; + } + + this.saveLastModified(response.headers); + + if (!isSuccessStatusCode(response.statusCode)) { + this.handleError(response.statusCode); + return Promise.reject(new Error()); + } + + const datafile = this.getDatafileFromResponse(response); + if (datafile) { + this.handleDatafile(datafile); + // if autoUpdate is off, don't need to sync datafile any more + if (!this.autoUpdate) { + this.repeater.stop(); + } + } + } + + private makeDatafileRequest(): AbortableRequest { + const headers: Headers = {}; + if (this.lastResponseLastModified) { + headers['if-modified-since'] = this.lastResponseLastModified; + } + + if (this.datafileAccessToken) { + this.logger?.debug('Adding Authorization header with Bearer Token'); + headers['Authorization'] = `Bearer ${this.datafileAccessToken}`; + } + + this.logger?.debug('Making datafile request to url %s with headers: %s', this.datafileUrl, () => JSON.stringify(headers)); + return this.requestHandler.makeRequest(this.datafileUrl, headers, 'GET'); + } + + private async syncDatafile(): Promise<void> { + this.currentRequest = this.makeDatafileRequest(); + return this.currentRequest.responsePromise + .then(this.onRequestResolved.bind(this), this.onRequestRejected.bind(this)) + .finally(() => this.currentRequest = undefined); + } + + private handleDatafile(datafile: string): void { + if (this.isDone()) { + return; + } + + this.currentDatafile = datafile; + this.cache?.set(this.cacheKey, datafile); + + if (this.isStarting()) { + this.startPromise.resolve(); + this.state = ServiceState.Running; + } + this.emitter.emit('update', datafile); + } + + private getDatafileFromResponse(response: Response): string | undefined{ + this.logger?.debug('Response status code: %s', response.statusCode); + if (response.statusCode === 304) { + return undefined; + } + return response.body; + } + + private saveLastModified(headers: Headers): void { + const lastModifiedHeader = headers['last-modified'] || headers['Last-Modified']; + if (lastModifiedHeader !== undefined) { + this.lastResponseLastModified = lastModifiedHeader; + this.logger?.debug('Saved last modified header value from response: %s', this.lastResponseLastModified); + } + } + + private setDatafileFromCacheIfAvailable(): void { + this.cache?.get(this.cacheKey).then(datafile => { + if (datafile && this.isStarting()) { + this.handleDatafile(datafile); + } + }).catch(() => {}); + } +} diff --git a/lib/core/project_config/index.tests.js b/lib/project_config/project_config.tests.js similarity index 96% rename from lib/core/project_config/index.tests.js rename to lib/project_config/project_config.tests.js index 24134e3cd..c49a75dad 100644 --- a/lib/core/project_config/index.tests.js +++ b/lib/project_config/project_config.tests.js @@ -16,15 +16,15 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { forEach, cloneDeep } from 'lodash'; -import { sprintf } from '../../utils/fns'; -import { getLogger } from '../../modules/logging'; +import { sprintf } from '../utils/fns'; +import { getLogger } from '../modules/logging'; -import fns from '../../utils/fns'; -import projectConfig from './'; -import { ERROR_MESSAGES, FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../../utils/enums'; -import * as loggerPlugin from '../../plugins/logger'; -import testDatafile from '../../tests/test_data'; -import configValidator from '../../utils/config_validator'; +import fns from '../utils/fns'; +import projectConfig from './project_config'; +import { ERROR_MESSAGES, FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; +import * as loggerPlugin from '../plugins/logger'; +import testDatafile from '../tests/test_data'; +import configValidator from '../utils/config_validator'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var logger = getLogger(); @@ -874,9 +874,7 @@ describe('lib/core/project_config', function() { describe('#tryCreatingProjectConfig', function() { var stubJsonSchemaValidator; beforeEach(function() { - stubJsonSchemaValidator = { - validate: sinon.stub().returns(true), - }; + stubJsonSchemaValidator = sinon.stub().returns(true); sinon.stub(configValidator, 'validateDatafile').returns(true); sinon.spy(logger, 'error'); }); @@ -900,7 +898,7 @@ describe('#tryCreatingProjectConfig', function() { }, }; - stubJsonSchemaValidator.validate.returns(true); + stubJsonSchemaValidator.returns(true); var result = projectConfig.tryCreatingProjectConfig({ datafile: configDatafile, @@ -908,29 +906,31 @@ describe('#tryCreatingProjectConfig', function() { logger: logger, }); - assert.deepInclude(result.configObj, configObj); + assert.deepInclude(result, configObj); }); - it('returns an error when validateDatafile throws', function() { + it('throws an error when validateDatafile throws', function() { configValidator.validateDatafile.throws(); - stubJsonSchemaValidator.validate.returns(true); - var { error } = projectConfig.tryCreatingProjectConfig({ - datafile: { foo: 'bar' }, - jsonSchemaValidator: stubJsonSchemaValidator, - logger: logger, + stubJsonSchemaValidator.returns(true); + assert.throws(() => { + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: stubJsonSchemaValidator, + logger: logger, + }); }); - assert.isNotNull(error); }); - it('returns an error when jsonSchemaValidator.validate throws', function() { + it('throws an error when jsonSchemaValidator.validate throws', function() { configValidator.validateDatafile.returns(true); - stubJsonSchemaValidator.validate.throws(); - var { error } = projectConfig.tryCreatingProjectConfig({ - datafile: { foo: 'bar' }, - jsonSchemaValidator: stubJsonSchemaValidator, - logger: logger, + stubJsonSchemaValidator.throws(); + assert.throws(() => { + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: stubJsonSchemaValidator, + logger: logger, + }); }); - assert.isNotNull(error); }); it('skips json validation when jsonSchemaValidator is not provided', function() { @@ -954,7 +954,7 @@ describe('#tryCreatingProjectConfig', function() { logger: logger, }); - assert.deepInclude(result.configObj, configObj); + assert.deepInclude(result, configObj); sinon.assert.notCalled(logger.error); }); }); diff --git a/lib/core/project_config/index.ts b/lib/project_config/project_config.ts similarity index 96% rename from lib/core/project_config/index.ts rename to lib/project_config/project_config.ts index 68ffbeacd..a31dabe5e 100644 --- a/lib/core/project_config/index.ts +++ b/lib/project_config/project_config.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { find, objectEntries, objectValues, sprintf, assign, keyBy } from '../../utils/fns'; +import { find, objectEntries, objectValues, sprintf, assign, keyBy } from '../utils/fns'; -import { ERROR_MESSAGES, LOG_LEVEL, LOG_MESSAGES, FEATURE_VARIABLE_TYPES } from '../../utils/enums'; -import configValidator from '../../utils/config_validator'; +import { ERROR_MESSAGES, LOG_LEVEL, LOG_MESSAGES, FEATURE_VARIABLE_TYPES } from '../utils/enums'; +import configValidator from '../utils/config_validator'; -import { LogHandler } from '../../modules/logging'; +import { LogHandler } from '../modules/logging'; import { Audience, Experiment, @@ -33,17 +33,16 @@ import { VariationVariable, Integration, FeatureVariableValue, -} from '../../shared_types'; -import { OdpConfig, OdpIntegrationConfig } from '../odp/odp_config'; +} from '../shared_types'; +import { OdpConfig, OdpIntegrationConfig } from '../core/odp/odp_config'; +import { Transformer } from '../utils/type'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type // eslint-disable-next-line @typescript-eslint/ban-types datafile: string | object; - jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean; - }; - logger: LogHandler; + jsonSchemaValidator?: Transformer<unknown, boolean>; + logger?: LogHandler; } interface Event { @@ -797,34 +796,25 @@ export const toDatafile = function(projectConfig: ProjectConfig): string { /** * Try to create a project config object from the given datafile and * configuration properties. - * Returns an object with configObj and error properties. - * If successful, configObj is the project config object, and error is null. - * Otherwise, configObj is null and error is an error with more information. + * Returns a ProjectConfig if successful. + * Otherwise, throws an error. * @param {Object} config * @param {Object|string} config.datafile * @param {Object} config.jsonSchemaValidator * @param {Object} config.logger - * @returns {Object} Object containing configObj and error properties + * @returns {Object} ProjectConfig + * @throws {Error} */ export const tryCreatingProjectConfig = function( config: TryCreatingProjectConfigConfig -): { configObj: ProjectConfig | null; error: Error | null } { - let newDatafileObj; - try { - newDatafileObj = configValidator.validateDatafile(config.datafile); - } catch (error) { - return { configObj: null, error }; - } +): ProjectConfig { + const newDatafileObj = configValidator.validateDatafile(config.datafile); if (config.jsonSchemaValidator) { - try { - config.jsonSchemaValidator.validate(newDatafileObj); - config.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_DATAFILE, MODULE_NAME); - } catch (error) { - return { configObj: null, error }; - } + config.jsonSchemaValidator(newDatafileObj); + config.logger?.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_DATAFILE, MODULE_NAME); } else { - config.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.SKIPPING_JSON_VALIDATION, MODULE_NAME); + config.logger?.log(LOG_LEVEL.INFO, LOG_MESSAGES.SKIPPING_JSON_VALIDATION, MODULE_NAME); } const createProjectConfigArgs = [newDatafileObj]; @@ -834,11 +824,7 @@ export const tryCreatingProjectConfig = function( } const newConfigObj = createProjectConfig(...createProjectConfigArgs); - - return { - configObj: newConfigObj, - error: null, - }; + return newConfigObj; }; /** diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts new file mode 100644 index 000000000..5a568188d --- /dev/null +++ b/lib/project_config/project_config_manager.spec.ts @@ -0,0 +1,522 @@ +/** + * Copyright 2019-2020, 2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; +import { ProjectConfigManagerImpl } from './project_config_manager'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { ServiceState } from '../service'; +import * as testData from '../tests/test_data'; +import { createProjectConfig } from './project_config'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { getMockDatafileManager } from '../tests/mock/mock_datafile_manager'; +import { wait } from '../../tests/testUtils'; + +const cloneDeep = (x: any) => JSON.parse(JSON.stringify(x)); + +describe('ProjectConfigManagerImpl', () => { + it('should reject onRunning() and log error if neither datafile nor a datafileManager is passed into the constructor', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should set status to Failed if neither datafile nor a datafileManager is passed into the constructor', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(manager.getState()).toBe(ServiceState.Failed); + }); + + it('should reject onTerminated if neither datafile nor a datafileManager is passed into the constructor', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + describe('when constructed with only a datafile', () => { + it('should reject onRunning() and log error if the datafile is invalid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: {}}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should set status to Failed if the datafile is invalid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: {}}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(manager.getState()).toBe(ServiceState.Failed); + }); + + it('should reject onTerminated if the datafile is invalid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('should fulfill onRunning() and set status to Running if the datafile is valid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: testData.getTestProjectConfig()}); + manager.start(); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getState()).toBe(ServiceState.Running); + }); + + it('should call onUpdate listeners registered before or after start() with the project config after resolving onRunning()', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: testData.getTestProjectConfig()}); + const listener1 = vi.fn(); + manager.onUpdate(listener1); + manager.start(); + const listener2 = vi.fn(); + manager.onUpdate(listener2); + expect(listener1).not.toHaveBeenCalled(); + expect(listener2).not.toHaveBeenCalledOnce(); + + await manager.onRunning(); + + expect(listener1).toHaveBeenCalledOnce(); + expect(listener2).toHaveBeenCalledOnce(); + + expect(listener1).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + expect(listener2).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return the correct config from getConfig() both before or after onRunning() resolves', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: testData.getTestProjectConfig()}); + manager.start(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + + describe('when constructed with a datafileManager', () => { + describe('when datafile is also provided', () => { + describe('when datafile is valid', () => { + it('should resolve onRunning() before datafileManger.onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ + onRunning: resolvablePromise<void>().promise, // this will not be resolved + }); + vi.spyOn(datafileManager, 'onRunning'); + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.start(); + + expect(datafileManager.onRunning).toHaveBeenCalled(); + await expect(manager.onRunning()).resolves.not.toThrow(); + }); + + it('should resolve onRunning() even if datafileManger.onRunning() rejects', async () => { + const onRunning = Promise.reject(new Error('onRunning error')); + const datafileManager = getMockDatafileManager({ + onRunning, + }); + vi.spyOn(datafileManager, 'onRunning'); + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.start(); + + expect(datafileManager.onRunning).toHaveBeenCalled(); + await expect(manager.onRunning()).resolves.not.toThrow(); + }); + + it('should call the onUpdate handler before datafileManger.onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ + onRunning: resolvablePromise<void>().promise, // this will not be resolved + }); + + const listener = vi.fn(); + + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.start(); + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return the correct config from getConfig() both before or after onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ + onRunning: resolvablePromise<void>().promise, // this will not be resolved + }); + + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.start(); + + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + + describe('when datafile is invalid', () => { + it('should reject onRunning() with the same error if datafileManager.onRunning() rejects', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.reject('test error') }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + await expect(manager.onRunning()).rejects.toBe('test error'); + }); + + it('should resolve onRunning() if datafileManager.onUpdate() is fired and should update config', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should resolve onRunning(), update config and call onUpdate listeners if datafileManager.onUpdate() is fired', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + const listener = vi.fn(); + manager.onUpdate(listener); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return undefined from getConfig() before onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + expect(manager.getConfig()).toBeUndefined(); + }); + + it('should return the correct config from getConfig() after onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + }); + + describe('when datafile is not provided', () => { + it('should reject onRunning() if datafileManager.onRunning() rejects', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.reject('test error') }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + await expect(manager.onRunning()).rejects.toBe('test error'); + }); + + it('should reject onRunning() and onTerminated if datafileManager emits an invalid datafile in the first onUpdate', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate('foo'); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('should resolve onRunning(), update config and call onUpdate listeners if datafileManager.onUpdate() is fired', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + const listener = vi.fn(); + manager.onUpdate(listener); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return undefined from getConfig() before onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + expect(manager.getConfig()).toBeUndefined(); + }); + + it('should return the correct config from getConfig() after onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + + it('should update the config and call onUpdate handlers when datafileManager onUpdate is fired with valid datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const datafile = testData.getTestProjectConfig(); + const manager = new ProjectConfigManagerImpl({ datafile, datafileManager }); + manager.start(); + + const listener = vi.fn(); + manager.onUpdate(listener); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenNthCalledWith(1, createProjectConfig(datafile)); + + const updatedDatafile = cloneDeep(datafile); + updatedDatafile['revision'] = '99'; + datafileManager.pushUpdate(updatedDatafile); + await Promise.resolve(); + + expect(manager.getConfig()).toEqual(createProjectConfig(updatedDatafile)); + expect(listener).toHaveBeenNthCalledWith(2, createProjectConfig(updatedDatafile)); + }); + + it('should not call onUpdate handlers and should log error when datafileManager onUpdate is fired with invalid datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const logger = getMockLogger(); + const datafile = testData.getTestProjectConfig(); + const manager = new ProjectConfigManagerImpl({ logger, datafile, datafileManager }); + manager.start(); + + const listener = vi.fn(); + manager.onUpdate(listener); + + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + + expect(listener).toHaveBeenCalledWith(createProjectConfig(datafile)); + + const updatedDatafile = {}; + datafileManager.pushUpdate(updatedDatafile); + await Promise.resolve(); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should use the JSON schema validator to validate the datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const datafile = testData.getTestProjectConfig(); + const jsonSchemaValidator = vi.fn().mockReturnValue(true); + const manager = new ProjectConfigManagerImpl({ datafile, datafileManager, jsonSchemaValidator }); + manager.start(); + + await manager.onRunning(); + + const updatedDatafile = cloneDeep(datafile); + updatedDatafile['revision'] = '99'; + datafileManager.pushUpdate(updatedDatafile); + await Promise.resolve(); + + expect(jsonSchemaValidator).toHaveBeenCalledTimes(2); + expect(jsonSchemaValidator).toHaveBeenNthCalledWith(1, datafile); + expect(jsonSchemaValidator).toHaveBeenNthCalledWith(2, updatedDatafile); + }); + + it('should not call onUpdate handlers when datafileManager onUpdate is fired with the same datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const datafile = testData.getTestProjectConfig(); + const manager = new ProjectConfigManagerImpl({ datafile, datafileManager }); + manager.start(); + + const listener = vi.fn(); + manager.onUpdate(listener); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenNthCalledWith(1, createProjectConfig(datafile)); + + datafileManager.pushUpdate(cloneDeep(datafile)); + await Promise.resolve(); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('should remove onUpdate handlers when the returned fuction is called', async () => { + const datafile = testData.getTestProjectConfig(); + const datafileManager = getMockDatafileManager({}); + + const manager = new ProjectConfigManagerImpl({ datafile }); + manager.start(); + + const listener = vi.fn(); + const dispose = manager.onUpdate(listener); + + await manager.onRunning(); + expect(listener).toHaveBeenNthCalledWith(1, createProjectConfig(datafile)); + + dispose(); + + datafileManager.pushUpdate(cloneDeep(testData.getTestProjectConfigWithFeatures())); + await Promise.resolve(); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('should work with datafile specified as string', async () => { + const datafile = testData.getTestProjectConfig(); + + const manager = new ProjectConfigManagerImpl({ datafile: JSON.stringify(datafile) }); + manager.start(); + + const listener = vi.fn(); + manager.onUpdate(listener); + + await manager.onRunning(); + expect(listener).toHaveBeenCalledWith(createProjectConfig(datafile)); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + }); + + it('should reject onRunning() and log error if the datafile string is an invalid json', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: 'foo'}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should reject onRunning() and log error if the datafile version is not supported', async () => { + const logger = getMockLogger(); + const datafile = testData.getUnsupportedVersionConfig(); + const manager = new ProjectConfigManagerImpl({ logger, datafile }); + manager.start(); + + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + describe('stop()', () => { + it('should reject onRunning() if stop is called when the datafileManager state is New', async () => { + const datafileManager = getMockDatafileManager({}); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + + expect(manager.getState()).toBe(ServiceState.New); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('should reject onRunning() if stop is called when the datafileManager state is Starting', async () => { + const datafileManager = getMockDatafileManager({}); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + + manager.start(); + expect(manager.getState()).toBe(ServiceState.Starting); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('should call datafileManager.stop()', async () => { + const datafileManager = getMockDatafileManager({}); + const spy = vi.spyOn(datafileManager, 'stop'); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + manager.stop(); + expect(spy).toHaveBeenCalled(); + }); + + it('should set status to Terminated immediately if no datafile manager is provided and resolve onTerminated', async () => { + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig() }); + manager.stop(); + expect(manager.getState()).toBe(ServiceState.Terminated); + await expect(manager.onTerminated()).resolves.not.toThrow(); + }); + + it('should set status to Stopping while awaiting for datafileManager onTerminated', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + manager.stop(); + + for (let i = 0; i < 100; i++) { + expect(manager.getState()).toBe(ServiceState.Stopping); + await wait(0); + } + }); + + it('should set status to Terminated and resolve onTerminated after datafileManager.onTerminated() resolves', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + manager.stop(); + + for (let i = 0; i < 50; i++) { + expect(manager.getState()).toBe(ServiceState.Stopping); + await wait(0); + } + + datafileManagerTerminated.resolve(); + await expect(manager.onTerminated()).resolves.not.toThrow(); + expect(manager.getState()).toBe(ServiceState.Terminated); + }); + + it('should set status to Failed and reject onTerminated after datafileManager.onTerminated() rejects', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + manager.stop(); + + for (let i = 0; i < 50; i++) { + expect(manager.getState()).toBe(ServiceState.Stopping); + await wait(0); + } + + datafileManagerTerminated.reject(); + await expect(manager.onTerminated()).rejects.toThrow(); + expect(manager.getState()).toBe(ServiceState.Failed); + }); + + it('should not call onUpdate handlers after stop is called', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + const listener = vi.fn(); + manager.onUpdate(listener); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + + expect(listener).toHaveBeenCalledTimes(1); + manager.stop(); + datafileManager.pushUpdate(testData.getTestProjectConfigWithFeatures()); + + datafileManagerTerminated.resolve(); + await expect(manager.onTerminated()).resolves.not.toThrow(); + + expect(listener).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts new file mode 100644 index 000000000..c03ee9b4c --- /dev/null +++ b/lib/project_config/project_config_manager.ts @@ -0,0 +1,219 @@ +/** + * Copyright 2019-2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from '../modules/logging'; +import { createOptimizelyConfig } from '../core/optimizely_config'; +import { OptimizelyConfig } from '../shared_types'; +import { DatafileManager } from './datafile_manager'; +import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from './project_config'; +import { scheduleMicrotask } from '../utils/microtask'; +import { Service, ServiceState, BaseService } from '../service'; +import { Consumer, Fn, Transformer } from '../utils/type'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; + +interface ProjectConfigManagerConfig { + // TODO: Don't use object type + // eslint-disable-next-line @typescript-eslint/ban-types + datafile?: string | object; + jsonSchemaValidator?: Transformer<unknown, boolean>, + datafileManager?: DatafileManager; + logger?: LoggerFacade; +} + +export interface ProjectConfigManager extends Service { + setLogger(logger: LoggerFacade): void; + getConfig(): ProjectConfig | undefined; + getOptimizelyConfig(): OptimizelyConfig | undefined; + onUpdate(listener: Consumer<ProjectConfig>): Fn; +} + +/** + * ProjectConfigManager provides project config objects via its methods + * getConfig and onUpdate. It uses a DatafileManager to fetch datafile if provided. + * It is responsible for parsing and validating datafiles, and converting datafile + * string into project config objects. + * @param {ProjectConfigManagerConfig} config + */ +export class ProjectConfigManagerImpl extends BaseService implements ProjectConfigManager { + private datafile?: string | object; + private projectConfig?: ProjectConfig; + private optimizelyConfig?: OptimizelyConfig; + public jsonSchemaValidator?: Transformer<unknown, boolean>; + public datafileManager?: DatafileManager; + private eventEmitter: EventEmitter<{ update: ProjectConfig }> = new EventEmitter(); + private logger?: LoggerFacade; + + constructor(config: ProjectConfigManagerConfig) { + super(); + this.logger = config.logger; + this.jsonSchemaValidator = config.jsonSchemaValidator; + this.datafile = config.datafile; + this.datafileManager = config.datafileManager; + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + start(): void { + if (!this.isNew()) { + return; + } + + this.state = ServiceState.Starting; + if (!this.datafile && !this.datafileManager) { + // TODO: replace message with imported constants + this.handleInitError(new Error('You must provide at least one of sdkKey or datafile')); + return; + } + + if (this.datafile) { + this.handleNewDatafile(this.datafile, true); + } + + this.datafileManager?.start(); + + // This handles the case where the datafile manager starts successfully. The + // datafile manager will only start successfully when it has downloaded a datafile, + // an will fire an onUpdate event. + this.datafileManager?.onUpdate(this.handleNewDatafile.bind(this)); + + // If the datafile manager runs successfully, it will emit a onUpdate event. We can + // handle the success case in the onUpdate handler. Hanlding the error case in the + // catch callback + this.datafileManager?.onRunning().catch((err) => { + this.handleDatafileManagerError(err); + }); + } + + private handleInitError(error: Error): void { + this.logger?.error(error); + this.state = ServiceState.Failed; + this.datafileManager?.stop(); + this.startPromise.reject(error); + this.stopPromise.reject(error); + } + + private handleDatafileManagerError(err: Error): void { + // TODO: replace message with imported constants + this.logger?.error('datafile manager failed to start', err); + + // If datafile manager onRunning() promise is rejected, and the project config manager + // is still in starting state, that means a datafile was not provided in cofig or was invalid, + // otherwise the state would have already been set to running synchronously. + // In this case, we cannot recover. + if (this.isStarting()) { + this.handleInitError(err); + } + } + + /** + * Handle new datafile by attemping to create a new Project Config object. If successful and + * the new config object's revision is newer than the current one, sets/updates the project config + * and emits onUpdate event. If unsuccessful, + * the project config and optimizely config objects will not be updated. If the error + * is fatal, handleInitError will be called. + */ + private handleNewDatafile(newDatafile: string | object, fromConfig = false): void { + if (this.isDone()) { + return; + } + + try { + const config = tryCreatingProjectConfig({ + datafile: newDatafile, + jsonSchemaValidator: this.jsonSchemaValidator, + logger: this.logger, + }); + + if(this.isStarting()) { + this.state = ServiceState.Running; + this.startPromise.resolve(); + } + + if (this.projectConfig?.revision !== config.revision) { + this.projectConfig = config; + this.optimizelyConfig = undefined; + scheduleMicrotask(() => { + this.eventEmitter.emit('update', config); + }) + } + } catch (err) { + this.logger?.error(err); + + // if the state is starting and no datafileManager is provided, we cannot recover. + // If the state is starting and the datafileManager has emitted a datafile, + // that means a datafile was not provided in config or an invalid datafile was provided, + // otherwise the state would have already been set to running synchronously. + // If the first datafile emitted by the datafileManager is invalid, + // we consider this to be an initialization error as well. + const fatalError = (this.isStarting() && !this.datafileManager) || + (this.isStarting() && !fromConfig); + if (fatalError) { + this.handleInitError(err); + } + } + } + + getConfig(): ProjectConfig | undefined { + return this.projectConfig; + } + + getOptimizelyConfig(): OptimizelyConfig | undefined { + if (!this.optimizelyConfig && this.projectConfig) { + this.optimizelyConfig = createOptimizelyConfig(this.projectConfig, toDatafile(this.projectConfig), this.logger); + } + return this.optimizelyConfig; + } + + /** + * Add a listener for project config updates. The listener will be called + * whenever this instance has a new project config object available. + * Returns a dispose function that removes the subscription + * @param {Function} listener + * @return {Function} + */ + onUpdate(listener: Consumer<ProjectConfig>): Fn { + return this.eventEmitter.on('update', listener); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew() || this.isStarting()) { + // TOOD: replace message with imported constants + this.startPromise.reject(new Error('Datafile manager stopped before it could be started')); + } + + this.state = ServiceState.Stopping; + this.eventEmitter.removeAllListeners(); + if (!this.datafileManager) { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + return; + } + + this.datafileManager.stop(); + this.datafileManager.onTerminated().then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + }).catch((err) => { + this.state = ServiceState.Failed; + this.stopPromise.reject(err); + }); + } +} diff --git a/lib/core/project_config/project_config_schema.ts b/lib/project_config/project_config_schema.ts similarity index 99% rename from lib/core/project_config/project_config_schema.ts rename to lib/project_config/project_config_schema.ts index 70cecb3d4..c33f013ae 100644 --- a/lib/core/project_config/project_config_schema.ts +++ b/lib/project_config/project_config_schema.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2020, Optimizely + * Copyright 2016-2017, 2020, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/service.spec.ts b/lib/service.spec.ts new file mode 100644 index 000000000..1faae69ac --- /dev/null +++ b/lib/service.spec.ts @@ -0,0 +1,107 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { it, expect } from 'vitest'; +import { BaseService, ServiceState } from './service'; + +class TestService extends BaseService { + constructor() { + super(); + } + + start(): void { + this.setState(ServiceState.Running); + this.startPromise.resolve(); + } + + failStart(): void { + this.setState(ServiceState.Failed); + this.startPromise.reject(); + } + + stop(): void { + this.setState(ServiceState.Running); + this.startPromise.resolve(); + } + + failStop(): void { + this.setState(ServiceState.Failed); + this.startPromise.reject(); + } + + setState(state: ServiceState): void { + this.state = state; + } +} + + +it('should set state to New on construction', async () => { + const service = new TestService(); + expect(service.getState()).toBe(ServiceState.New); +}); + +it('should return correct state when getState() is called', () => { + const service = new TestService(); + expect(service.getState()).toBe(ServiceState.New); + service.setState(ServiceState.Running); + expect(service.getState()).toBe(ServiceState.Running); + service.setState(ServiceState.Terminated); + expect(service.getState()).toBe(ServiceState.Terminated); + service.setState(ServiceState.Failed); + expect(service.getState()).toBe(ServiceState.Failed); +}); + +it('should return an appropraite promise when onRunning() is called', () => { + const service1 = new TestService(); + const onRunning1 = service1.onRunning(); + + const service2 = new TestService(); + const onRunning2 = service2.onRunning(); + + return new Promise<void>((done) => { + Promise.all([ + onRunning1.then(() => { + expect(service1.getState()).toBe(ServiceState.Running); + }), onRunning2.catch(() => { + expect(service2.getState()).toBe(ServiceState.Failed); + }) + ]).then(() => done()); + + service1.start(); + service2.failStart(); + }); +}); + +it('should return an appropraite promise when onRunning() is called', () => { + const service1 = new TestService(); + const onRunning1 = service1.onRunning(); + + const service2 = new TestService(); + const onRunning2 = service2.onRunning(); + + return new Promise<void>((done) => { + Promise.all([ + onRunning1.then(() => { + expect(service1.getState()).toBe(ServiceState.Running); + }), onRunning2.catch(() => { + expect(service2.getState()).toBe(ServiceState.Failed); + }) + ]).then(() => done()); + + service1.start(); + service2.failStart(); + }); +}); diff --git a/lib/service.ts b/lib/service.ts new file mode 100644 index 000000000..48ad8fbff --- /dev/null +++ b/lib/service.ts @@ -0,0 +1,94 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolvablePromise, ResolvablePromise } from "./utils/promise/resolvablePromise"; + + +/** + * The service interface represents an object with an operational state, + * with methods to start and stop. The design of this interface in modelled + * after Guava Service interface (https://github.com/google/guava/wiki/ServiceExplained). + */ + +export enum ServiceState { + New, + Starting, + Running, + Stopping, + Terminated, + Failed, +} + +export interface Service { + getState(): ServiceState; + start(): void; + // onRunning will reject if the service fails to start + // or stopped before it could start. + // It will resolve if the service is starts successfully. + onRunning(): Promise<void>; + stop(): void; + // onTerminated will reject if the service enters a failed state + // either by failing to start or stop. + // It will resolve if the service is stopped successfully. + onTerminated(): Promise<void>; +} + +export abstract class BaseService implements Service { + protected state: ServiceState; + protected startPromise: ResolvablePromise<void>; + protected stopPromise: ResolvablePromise<void>; + + constructor() { + this.state = ServiceState.New; + this.startPromise = resolvablePromise(); + this.stopPromise = resolvablePromise(); + + // avoid unhandled promise rejection + this.startPromise.promise.catch(() => {}); + this.stopPromise.promise.catch(() => {}); + } + + onRunning(): Promise<void> { + return this.startPromise.promise; + } + + onTerminated(): Promise<void> { + return this.stopPromise.promise; + } + + getState(): ServiceState { + return this.state; + } + + isStarting(): boolean { + return this.state === ServiceState.Starting; + } + + isNew(): boolean { + return this.state === ServiceState.New; + } + + isDone(): boolean { + return [ + ServiceState.Stopping, + ServiceState.Terminated, + ServiceState.Failed + ].includes(this.state); + } + + abstract start(): void; + abstract stop(): void; +} diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 08291ecf2..69c1080d3 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -37,7 +37,8 @@ import { IOdpEventManager } from './core/odp/odp_event_manager'; import { IOdpManager } from './core/odp/odp_manager'; import { IUserAgentParser } from './core/odp/user_agent_parser'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; -import { ProjectConfig } from './core/project_config'; +import { ProjectConfig } from './project_config/project_config'; +import { ProjectConfigManager } from './project_config/project_config_manager'; export interface BucketerParams { experimentId: string; @@ -281,6 +282,7 @@ export enum OptimizelyDecideOption { * options required to create optimizely object */ export interface OptimizelyOptions { + projectConfigManager: ProjectConfigManager; UNSTABLE_conditionEvaluators?: unknown; clientEngine: string; clientVersion?: string; @@ -372,7 +374,7 @@ export interface Client { attributes?: UserAttributes ): { [variableKey: string]: unknown } | null; getOptimizelyConfig(): OptimizelyConfig | null; - onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }>; + onReady(options?: { timeout?: number }): Promise<unknown>; close(): Promise<{ success: boolean; reason?: string }>; sendOdpEvent(action: string, type?: string, identifiers?: Map<string, string>, data?: Map<string, unknown>): void; getProjectConfig(): ProjectConfig | null; @@ -398,7 +400,6 @@ export type PersistentCacheProvider = () => PersistentCache; * For compatibility with the previous declaration file */ export interface Config extends ConfigLite { - datafileOptions?: DatafileOptions; // Options for Datafile Manager eventBatchSize?: number; // Maximum size of events to be dispatched in a batch eventFlushInterval?: number; // Maximum time for an event to be enqueued eventMaxQueueSize?: number; // Maximum size for the event queue @@ -412,10 +413,7 @@ export interface Config extends ConfigLite { * For compatibility with the previous declaration file */ export interface ConfigLite { - // Datafile string - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: object | string; + projectConfigManager: ProjectConfigManager; // errorHandler object for logging error errorHandler?: ErrorHandler; // event dispatcher function diff --git a/lib/tests/mock/mock_datafile_manager.ts b/lib/tests/mock/mock_datafile_manager.ts new file mode 100644 index 000000000..f2aa450b9 --- /dev/null +++ b/lib/tests/mock/mock_datafile_manager.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Consumer } from '../../utils/type'; +import { DatafileManager } from '../../project_config/datafile_manager'; +import { EventEmitter } from '../../utils/event_emitter/event_emitter'; +import { BaseService } from '../../service'; +import { LoggerFacade } from '../../modules/logging'; + +type MockConfig = { + datafile?: string | object; + onRunning?: Promise<void>, + onTerminated?: Promise<void>, +} + +class MockDatafileManager extends BaseService implements DatafileManager { + eventEmitter: EventEmitter<{ update: string}> = new EventEmitter(); + datafile: string | object | undefined; + + constructor(opt: MockConfig) { + super(); + this.datafile = opt.datafile; + this.startPromise.resolve(opt.onRunning || Promise.resolve()); + this.stopPromise.resolve(opt.onTerminated || Promise.resolve()); + } + + start(): void { + return; + } + + stop(): void { + return; + } + + setLogger(logger: LoggerFacade): void { + } + + get(): string | undefined { + if (typeof this.datafile === 'object') { + return JSON.stringify(this.datafile); + } + return this.datafile; + } + + setDatafile(datafile: string): void { + this.datafile = datafile; + } + + onUpdate(listener: Consumer<string>): () => void { + return this.eventEmitter.on('update', listener) + } + + pushUpdate(datafile: string | object): void { + if (typeof datafile === 'object') { + datafile = JSON.stringify(datafile); + } + this.datafile = datafile; + this.eventEmitter.emit('update', datafile); + } +} + +export const getMockDatafileManager = (opt: MockConfig): MockDatafileManager => { + return new MockDatafileManager(opt); +}; diff --git a/lib/modules/datafile-manager/index.browser.ts b/lib/tests/mock/mock_logger.ts similarity index 61% rename from lib/modules/datafile-manager/index.browser.ts rename to lib/tests/mock/mock_logger.ts index 78a6879d5..7af7d26e8 100644 --- a/lib/modules/datafile-manager/index.browser.ts +++ b/lib/tests/mock/mock_logger.ts @@ -1,11 +1,11 @@ /** - * Copyright 2022, Optimizely + * Copyright 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,5 +14,15 @@ * limitations under the License. */ -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './browserDatafileManager'; +import { vi } from 'vitest'; +import { LoggerFacade } from '../../modules/logging'; + +export const getMockLogger = () : LoggerFacade => { + return { + info: vi.fn(), + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + }; +}; diff --git a/lib/tests/mock/mock_project_config_manager.ts b/lib/tests/mock/mock_project_config_manager.ts new file mode 100644 index 000000000..af7a8ba84 --- /dev/null +++ b/lib/tests/mock/mock_project_config_manager.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ProjectConfigManager } from '../../project_config/project_config_manager'; +import { ProjectConfig } from '../../project_config/project_config'; +import { Consumer } from '../../utils/type'; + +type MockOpt = { + initConfig?: ProjectConfig, + onRunning?: Promise<void>, + onTerminated?: Promise<void>, +} + +export const getMockProjectConfigManager = (opt: MockOpt = {}): ProjectConfigManager => { + return { + config: opt.initConfig, + start: () => {}, + onRunning: () => opt.onRunning || Promise.resolve(), + stop: () => {}, + onTerminated: () => opt.onTerminated || Promise.resolve(), + getConfig: function() { + return this.config; + }, + setConfig: function(config: ProjectConfig) { + this.config = config; + }, + onUpdate: function(listener: Consumer<ProjectConfig>) { + if (this.listeners === undefined) { + this.listeners = []; + } + this.listeners.push(listener); + return () => {}; + }, + pushUpdate: function(config: ProjectConfig) { + this.listeners.forEach((listener: any) => listener(config)); + } + } as any as ProjectConfigManager; +}; diff --git a/lib/tests/mock/mock_repeater.ts b/lib/tests/mock/mock_repeater.ts new file mode 100644 index 000000000..3f330f000 --- /dev/null +++ b/lib/tests/mock/mock_repeater.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi } from 'vitest'; + +import { Repeater } from '../../utils/repeater/repeater'; +import { AsyncTransformer } from '../../utils/type'; + +export class MockRepeater implements Repeater { + private handler?: AsyncTransformer<number, void>; + + start(): void { + } + + stop(): void { + } + + reset(): void { + } + + setTask(handler: AsyncTransformer<number, void>): void { + this.handler = handler; + } + + pushTick(failureCount: number): void { + this.handler?.(failureCount); + } +} + +//ignore ts no return type error +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const getMockRepeater = () => { + const mock = { + isRunning: false, + handler: undefined as any, + start: vi.fn(), + stop: vi.fn(), + reset: vi.fn(), + setTask(handler: AsyncTransformer<number, void>) { + this.handler = handler; + }, + // throw if not running. This ensures tests cannot + // do mock exection when the repeater is supposed to be not running. + execute(failureCount: number): Promise<void> { + if (!this.isRunning) throw new Error(); + const ret = this.handler?.(failureCount); + ret?.catch(() => {}); + return ret; + }, + }; + mock.start.mockImplementation(() => mock.isRunning = true); + mock.stop.mockImplementation(() => mock.isRunning = false); + return mock; +} diff --git a/lib/tests/mock/mock_request_handler.ts b/lib/tests/mock/mock_request_handler.ts new file mode 100644 index 000000000..3369bf125 --- /dev/null +++ b/lib/tests/mock/mock_request_handler.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi } from 'vitest'; +import { AbortableRequest, Response } from '../../utils/http_request_handler/http'; +import { ResolvablePromise, resolvablePromise } from '../../utils/promise/resolvablePromise'; + + +export type MockAbortableRequest = AbortableRequest & { + mockResponse: ResolvablePromise<Response>; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const getMockAbortableRequest = (res?: Promise<Response>) => { + const response = resolvablePromise<Response>(); + if (res) response.resolve(res); + return { + mockResponse: response, + responsePromise: response.promise, + abort: vi.fn(), + }; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const getMockRequestHandler = () => { + const mock = { + makeRequest: vi.fn(), + } + return mock; +} diff --git a/lib/tests/test_data.js b/lib/tests/test_data.ts similarity index 99% rename from lib/tests/test_data.js rename to lib/tests/test_data.ts index eddf1a3fd..76d822eb8 100644 --- a/lib/tests/test_data.js +++ b/lib/tests/test_data.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2021, Optimizely + * Copyright 2016-2021, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import cloneDeep from 'lodash/cloneDeep'; -var config = { +/* eslint-disable */ +const cloneDeep = (x: any) => JSON.parse(JSON.stringify(x)); + +const config: any = { revision: '42', version: '2', events: [ diff --git a/lib/utils/event_emitter/event_emitter.spec.ts b/lib/utils/event_emitter/event_emitter.spec.ts new file mode 100644 index 000000000..fb5cfe441 --- /dev/null +++ b/lib/utils/event_emitter/event_emitter.spec.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { it, vi, expect } from 'vitest'; + +import { EventEmitter } from './event_emitter'; + +it('should call all registered listeners correctly on emit event', () => { + const emitter = new EventEmitter<{ foo: number, bar: string, baz: boolean}>(); + const fooListener1 = vi.fn(); + const fooListener2 = vi.fn(); + + emitter.on('foo', fooListener1); + emitter.on('foo', fooListener2); + + const barListener1 = vi.fn(); + const barListener2 = vi.fn(); + + emitter.on('bar', barListener1); + emitter.on('bar', barListener2); + + const bazListener = vi.fn(); + emitter.on('baz', bazListener); + + emitter.emit('foo', 1); + emitter.emit('bar', 'hello'); + + expect(fooListener1).toHaveBeenCalledOnce(); + expect(fooListener1).toHaveBeenCalledWith(1); + expect(fooListener2).toHaveBeenCalledOnce(); + expect(fooListener2).toHaveBeenCalledWith(1); + expect(barListener1).toHaveBeenCalledOnce(); + expect(barListener1).toHaveBeenCalledWith('hello'); + expect(barListener2).toHaveBeenCalledOnce(); + expect(barListener2).toHaveBeenCalledWith('hello'); + + expect(bazListener).not.toHaveBeenCalled(); +}); + +it('should remove listeners correctly when the function returned from on is called', () => { + const emitter = new EventEmitter<{ foo: number, bar: string }>(); + const fooListener1 = vi.fn(); + const fooListener2 = vi.fn(); + + const dispose = emitter.on('foo', fooListener1); + emitter.on('foo', fooListener2); + + const barListener1 = vi.fn(); + const barListener2 = vi.fn(); + + emitter.on('bar', barListener1); + emitter.on('bar', barListener2); + + dispose(); + emitter.emit('foo', 1); + emitter.emit('bar', 'hello'); + + expect(fooListener1).not.toHaveBeenCalled(); + expect(fooListener2).toHaveBeenCalledOnce(); + expect(fooListener2).toHaveBeenCalledWith(1); + expect(barListener1).toHaveBeenCalledWith('hello'); + expect(barListener1).toHaveBeenCalledWith('hello'); +}) + +it('should remove all listeners when removeAllListeners() is called', () => { + const emitter = new EventEmitter<{ foo: number, bar: string, baz: boolean}>(); + const fooListener1 = vi.fn(); + const fooListener2 = vi.fn(); + + emitter.on('foo', fooListener1); + emitter.on('foo', fooListener2); + + const barListener1 = vi.fn(); + const barListener2 = vi.fn(); + + emitter.on('bar', barListener1); + emitter.on('bar', barListener2); + + emitter.removeAllListeners(); + + emitter.emit('foo', 1); + emitter.emit('bar', 'hello'); + + expect(fooListener1).not.toHaveBeenCalled(); + expect(fooListener2).not.toHaveBeenCalled(); + expect(barListener1).not.toHaveBeenCalled(); + expect(barListener2).not.toHaveBeenCalled(); +}); diff --git a/lib/utils/event_emitter/event_emitter.ts b/lib/utils/event_emitter/event_emitter.ts new file mode 100644 index 000000000..22b22be5d --- /dev/null +++ b/lib/utils/event_emitter/event_emitter.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Fn } from "../type"; + +type Consumer<T> = (arg: T) => void; + +type Listeners<T> = { + [Key in keyof T]?: Map<number, Consumer<T[Key]>>; +}; + +export class EventEmitter<T> { + private id = 0; + private listeners: Listeners<T> = {} as Listeners<T>; + + on<E extends keyof T>(eventName: E, listener: Consumer<T[E]>): Fn { + if (!this.listeners[eventName]) { + this.listeners[eventName] = new Map(); + } + + const curId = this.id++; + this.listeners[eventName]?.set(curId, listener); + return () => { + this.listeners[eventName]?.delete(curId); + } + } + + emit<E extends keyof T>(eventName: E, data: T[E]): void { + const listeners = this.listeners[eventName]; + if (listeners) { + listeners.forEach(listener => { + listener(data); + }); + } + } + + removeAllListeners(): void { + this.listeners = {}; + } +} diff --git a/lib/utils/http_request_handler/browser_request_handler.ts b/lib/utils/http_request_handler/browser_request_handler.ts index 3a2fe73f0..a2756e318 100644 --- a/lib/utils/http_request_handler/browser_request_handler.ts +++ b/lib/utils/http_request_handler/browser_request_handler.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,12 +22,12 @@ import { REQUEST_TIMEOUT_MS } from '../enums'; * Handles sending requests and receiving responses over HTTP via XMLHttpRequest */ export class BrowserRequestHandler implements RequestHandler { - private readonly logger: LogHandler; - private readonly timeout: number; + private logger?: LogHandler; + private timeout: number; - public constructor(logger: LogHandler, timeout: number = REQUEST_TIMEOUT_MS) { - this.logger = logger; - this.timeout = timeout; + public constructor(opt: { logger?: LogHandler, timeout?: number } = {}) { + this.logger = opt.logger; + this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS; } /** @@ -67,7 +67,7 @@ export class BrowserRequestHandler implements RequestHandler { request.timeout = this.timeout; request.ontimeout = (): void => { - this.logger.log(LogLevel.WARNING, 'Request timed out'); + this.logger?.log(LogLevel.WARNING, 'Request timed out'); }; request.send(data); @@ -122,7 +122,7 @@ export class BrowserRequestHandler implements RequestHandler { } } } catch { - this.logger.log(LogLevel.WARNING, `Unable to parse & skipped header item '${headerLine}'`); + this.logger?.log(LogLevel.WARNING, `Unable to parse & skipped header item '${headerLine}'`); } }); return headers; diff --git a/lib/utils/http_request_handler/http.ts b/lib/utils/http_request_handler/http.ts index 98c1cedc6..ca7e63ae3 100644 --- a/lib/utils/http_request_handler/http.ts +++ b/lib/utils/http_request_handler/http.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, 2022 Optimizely + * Copyright 2019-2020, 2022, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ export interface Headers { * Simplified Response object containing only needed information */ export interface Response { - statusCode?: number; + statusCode: number; body: string; headers: Headers; } @@ -35,13 +35,14 @@ export interface Response { */ export interface AbortableRequest { abort(): void; - responsePromise: Promise<Response>; } +export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'; + /** * Client that handles sending requests and receiving responses */ export interface RequestHandler { - makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest; + makeRequest(requestUrl: string, headers: Headers, method: HttpMethod, data?: string): AbortableRequest; } diff --git a/lib/utils/http_request_handler/node_request_handler.ts b/lib/utils/http_request_handler/node_request_handler.ts index 458089540..26bc6cbda 100644 --- a/lib/utils/http_request_handler/node_request_handler.ts +++ b/lib/utils/http_request_handler/node_request_handler.ts @@ -25,12 +25,12 @@ import { REQUEST_TIMEOUT_MS } from '../enums'; * Handles sending requests and receiving responses over HTTP via NodeJS http module */ export class NodeRequestHandler implements RequestHandler { - private readonly logger: LogHandler; + private readonly logger?: LogHandler; private readonly timeout: number; - constructor(logger: LogHandler, timeout: number = REQUEST_TIMEOUT_MS) { - this.logger = logger; - this.timeout = timeout; + constructor(opt: { logger?: LogHandler, timeout?: number } = {}) { + this.logger = opt.logger; + this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS; } /** @@ -163,6 +163,11 @@ export class NodeRequestHandler implements RequestHandler { return; } + if (!incomingMessage.statusCode) { + reject(new Error('No status code in response')); + return; + } + resolve({ statusCode: incomingMessage.statusCode, body: responseData, diff --git a/lib/utils/json_schema_validator/index.tests.js b/lib/utils/json_schema_validator/index.tests.js index 597ce15b7..61df2abaa 100644 --- a/lib/utils/json_schema_validator/index.tests.js +++ b/lib/utils/json_schema_validator/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2020, 2022, Optimizely + * Copyright 2016-2020, 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { assert } from 'chai'; import { validate } from './'; import { ERROR_MESSAGES } from '../enums'; -import testData from '../../tests/test_data.js'; +import testData from '../../tests/test_data'; describe('lib/utils/json_schema_validator', function() { diff --git a/lib/utils/json_schema_validator/index.ts b/lib/utils/json_schema_validator/index.ts index fb164808e..7ad8708c9 100644 --- a/lib/utils/json_schema_validator/index.ts +++ b/lib/utils/json_schema_validator/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2020, 2022 Optimizely + * Copyright 2016-2017, 2020, 2022, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import { sprintf } from '../fns'; import { JSONSchema4, validate as jsonSchemaValidator } from 'json-schema'; import { ERROR_MESSAGES } from '../enums'; -import schema from '../../core/project_config/project_config_schema'; +import schema from '../../project_config/project_config_schema'; const MODULE_NAME = 'JSON_SCHEMA_VALIDATOR'; diff --git a/lib/utils/microtask/index.spec.ts b/lib/utils/microtask/index.spec.ts new file mode 100644 index 000000000..8d5fd9622 --- /dev/null +++ b/lib/utils/microtask/index.spec.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; +import { scheduleMicrotask } from '.'; + +describe('scheduleMicrotask', () => { + it('should use queueMicrotask if available', async () => { + expect(typeof globalThis.queueMicrotask).toEqual('function'); + const cb = vi.fn(); + scheduleMicrotask(cb); + await Promise.resolve(); + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('should polyfill if queueMicrotask is not available', async () => { + const originalQueueMicrotask = globalThis.queueMicrotask; + globalThis.queueMicrotask = undefined as any; // as any to pacify TS + + expect(globalThis.queueMicrotask).toBeUndefined(); + + const cb = vi.fn(); + scheduleMicrotask(cb); + await Promise.resolve(); + expect(cb).toHaveBeenCalledTimes(1); + + expect(globalThis.queueMicrotask).toBeUndefined(); + globalThis.queueMicrotask = originalQueueMicrotask; + }); +}); diff --git a/lib/utils/microtask/index.tests.js b/lib/utils/microtask/index.tests.js deleted file mode 100644 index 16091ad68..000000000 --- a/lib/utils/microtask/index.tests.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { scheduleMicrotaskOrTimeout } from './'; - -describe('scheduleMicrotaskOrTimeout', () => { - it('should use queueMicrotask if available', (done) => { - // Assuming queueMicrotask is available in the environment - scheduleMicrotaskOrTimeout(() => { - done(); - }); - }); - - it('should fallback to setTimeout if queueMicrotask is not available', (done) => { - // Temporarily remove queueMicrotask to test the fallback - const originalQueueMicrotask = window.queueMicrotask; - window.queueMicrotask = undefined; - - scheduleMicrotaskOrTimeout(() => { - // Restore queueMicrotask before calling done - window.queueMicrotask = originalQueueMicrotask; - done(); - }); - }); -}); diff --git a/lib/utils/microtask/index.ts b/lib/utils/microtask/index.ts index 816b17a27..02e2c474e 100644 --- a/lib/utils/microtask/index.ts +++ b/lib/utils/microtask/index.ts @@ -16,10 +16,10 @@ type Callback = () => void; -export const scheduleMicrotaskOrTimeout = (callback: Callback): void =>{ +export const scheduleMicrotask = (callback: Callback): void => { if (typeof queueMicrotask === 'function') { queueMicrotask(callback); } else { - setTimeout(callback); + Promise.resolve().then(callback); } -} \ No newline at end of file +} diff --git a/lib/utils/repeater/repeater.spec.ts b/lib/utils/repeater/repeater.spec.ts new file mode 100644 index 000000000..cebb17e38 --- /dev/null +++ b/lib/utils/repeater/repeater.spec.ts @@ -0,0 +1,284 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, vi, it, beforeEach, afterEach, describe } from 'vitest'; +import { ExponentialBackoff, IntervalRepeater } from './repeater'; +import { advanceTimersByTime } from '../../../tests/testUtils'; +import { ad } from 'vitest/dist/chunks/reporters.C_zwCd4j'; +import { resolvablePromise } from '../promise/resolvablePromise'; + +describe("ExponentialBackoff", () => { + it("should return the base with jitter on the first call", () => { + const exponentialBackoff = new ExponentialBackoff(5000, 10000, 1000); + const time = exponentialBackoff.backoff(); + + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + }); + + it('should use a random jitter within the specified limit', () => { + const exponentialBackoff1 = new ExponentialBackoff(5000, 10000, 1000); + const exponentialBackoff2 = new ExponentialBackoff(5000, 10000, 1000); + + const time = exponentialBackoff1.backoff(); + const time2 = exponentialBackoff2.backoff(); + + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + expect(time2).toBeGreaterThanOrEqual(5000); + expect(time2).toBeLessThanOrEqual(6000); + + expect(time).not.toEqual(time2); + }); + + it("should double the time when backoff() is called", () => { + const exponentialBackoff = new ExponentialBackoff(5000, 20000, 1000); + const time = exponentialBackoff.backoff(); + + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + const time2 = exponentialBackoff.backoff(); + expect(time2).toBeGreaterThanOrEqual(10000); + expect(time2).toBeLessThanOrEqual(11000); + + const time3 = exponentialBackoff.backoff(); + expect(time3).toBeGreaterThanOrEqual(20000); + expect(time3).toBeLessThanOrEqual(21000); + }); + + it('should not exceed the max time', () => { + const exponentialBackoff = new ExponentialBackoff(5000, 10000, 1000); + const time = exponentialBackoff.backoff(); + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + const time2 = exponentialBackoff.backoff(); + expect(time2).toBeGreaterThanOrEqual(10000); + expect(time2).toBeLessThanOrEqual(11000); + + const time3 = exponentialBackoff.backoff(); + expect(time3).toBeGreaterThanOrEqual(10000); + expect(time3).toBeLessThanOrEqual(11000); + }); + + it('should reset the backoff time when reset() is called', () => { + const exponentialBackoff = new ExponentialBackoff(5000, 10000, 1000); + const time = exponentialBackoff.backoff(); + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + exponentialBackoff.reset(); + const time2 = exponentialBackoff.backoff(); + expect(time2).toBeGreaterThanOrEqual(5000); + expect(time2).toBeLessThanOrEqual(6000); + }); +}); + + +describe("IntervalRepeater", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should call the handler at the specified interval', async() => { + const handler = vi.fn().mockResolvedValue(undefined); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(2); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(3); + }); + + it('should call the handler with correct failureCount value', async() => { + const handler = vi.fn().mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce(undefined) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce(undefined); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + expect(handler.mock.calls[0][0]).toBe(0); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(2); + expect(handler.mock.calls[1][0]).toBe(1); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(3); + expect(handler.mock.calls[2][0]).toBe(2); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(4); + expect(handler.mock.calls[3][0]).toBe(3); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(5); + expect(handler.mock.calls[4][0]).toBe(0); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(6); + expect(handler.mock.calls[5][0]).toBe(1); + }); + + it('should backoff when the handler fails if backoffController is provided', async() => { + const handler = vi.fn().mockRejectedValue(new Error()); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1100), + reset: vi.fn(), + }; + + const intervalRepeater = new IntervalRepeater(30000, backoffController); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(30000); + expect(handler).toHaveBeenCalledTimes(1); + expect(backoffController.backoff).toHaveBeenCalledTimes(1); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(2); + expect(backoffController.backoff).toHaveBeenCalledTimes(2); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(3); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + }); + + it('should use the regular interval when the handler fails if backoffController is not provided', async() => { + const handler = vi.fn().mockRejectedValue(new Error()); + + const intervalRepeater = new IntervalRepeater(30000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(30000); + expect(handler).toHaveBeenCalledTimes(1); + + await advanceTimersByTime(10000); + expect(handler).toHaveBeenCalledTimes(1); + await advanceTimersByTime(20000); + expect(handler).toHaveBeenCalledTimes(2); + }); + + it('should reset the backoffController after handler success', async () => { + const handler = vi.fn().mockRejectedValueOnce(new Error) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(undefined); + + const backoffController = { + + backoff: vi.fn().mockReturnValue(1100), + reset: vi.fn(), + }; + + const intervalRepeater = new IntervalRepeater(30000, backoffController); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(30000); + expect(handler).toHaveBeenCalledTimes(1); + expect(backoffController.backoff).toHaveBeenCalledTimes(1); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(2); + expect(backoffController.backoff).toHaveBeenCalledTimes(2); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(3); + + expect(backoffController.backoff).toHaveBeenCalledTimes(2); // backoff should not be called again + expect(backoffController.reset).toHaveBeenCalledTimes(1); // reset should be called once + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(3); // handler should be called after 30000ms + await advanceTimersByTime(30000 - 1100); + expect(handler).toHaveBeenCalledTimes(4); // handler should be called after 30000ms + }); + + + it('should wait for handler promise to resolve before scheduling another tick', async() => { + const ret = resolvablePromise(); + const handler = vi.fn().mockReturnValue(ret); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + + // should not schedule another call cause promise is pending + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + + ret.resolve(undefined); + await ret.promise; + + // Advance the timers to the next tick + await advanceTimersByTime(2000); + // The handler should be called again after the promise has resolved + expect(handler).toHaveBeenCalledTimes(2); + }); + + it('should not call the handler after stop is called', async() => { + const ret = resolvablePromise(); + const handler = vi.fn().mockReturnValue(ret); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + + intervalRepeater.stop(); + + ret.resolve(undefined); + await ret.promise; + + await advanceTimersByTime(2000); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/lib/utils/repeater/repeater.ts b/lib/utils/repeater/repeater.ts new file mode 100644 index 000000000..f758f0dc9 --- /dev/null +++ b/lib/utils/repeater/repeater.ts @@ -0,0 +1,136 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AsyncTransformer } from "../type"; +import { scheduleMicrotask } from "../microtask"; + +// A repeater will invoke the task repeatedly. The time at which the task is invoked +// is determined by the implementation. +// The task is a function that takes a number as an argument and returns a promise. +// The number argument is the number of times the task previously failed consecutively. +// If the retuned promise resolves, the repeater will assume the task succeeded, +// and will reset the failure count. If the promise is rejected, the repeater will +// assume the task failed and will increase the current consecutive failure count. +export interface Repeater { + // If immediateExecution is true, the first exection of + // the task will be immediate but asynchronous. + start(immediateExecution?: boolean): void; + stop(): void; + reset(): void; + setTask(task: AsyncTransformer<number, void>): void; +} + +export interface BackoffController { + backoff(): number; + reset(): void; +} + +export class ExponentialBackoff implements BackoffController { + private base: number; + private max: number; + private current: number; + private maxJitter: number; + + constructor(base: number, max: number, maxJitter: number) { + this.base = base; + this.max = max; + this.maxJitter = maxJitter; + this.current = base; + } + + backoff(): number { + const ret = this.current + this.maxJitter * Math.random(); + this.current = Math.min(this.current * 2, this.max); + return ret; + } + + reset(): void { + this.current = this.base; + } +} + +// IntervalRepeater is a Repeater that invokes the task at a fixed interval +// after the completion of the previous task invocation. If a backoff controller +// is provided, the repeater will use the backoff controller to determine the +// time between invocations after a failure instead. It will reset the backoffController +// on success. + +export class IntervalRepeater implements Repeater { + private timeoutId?: NodeJS.Timeout; + private task?: AsyncTransformer<number, void>; + private interval: number; + private failureCount = 0; + private backoffController?: BackoffController; + private isRunning = false; + + constructor(interval: number, backoffController?: BackoffController) { + this.interval = interval; + this.backoffController = backoffController; + } + + private handleSuccess() { + this.failureCount = 0; + this.backoffController?.reset(); + this.setTimer(this.interval); + } + + private handleFailure() { + this.failureCount++; + const time = this.backoffController?.backoff() ?? this.interval; + this.setTimer(time); + } + + private setTimer(timeout: number) { + if (!this.isRunning){ + return; + } + this.timeoutId = setTimeout(this.executeTask.bind(this), timeout); + } + + private executeTask() { + if (!this.task) { + return; + } + this.task(this.failureCount).then( + this.handleSuccess.bind(this), + this.handleFailure.bind(this) + ); + } + + start(immediateExecution?: boolean): void { + this.isRunning = true; + if(immediateExecution) { + scheduleMicrotask(this.executeTask.bind(this)); + } else { + this.setTimer(this.interval); + } + } + + stop(): void { + this.isRunning = false; + clearInterval(this.timeoutId); + } + + reset(): void { + this.failureCount = 0; + this.backoffController?.reset(); + this.stop(); + } + + setTask(task: AsyncTransformer<number, void>): void { + this.task = task; + } +} diff --git a/lib/modules/datafile-manager/index.node.ts b/lib/utils/type.ts similarity index 61% rename from lib/modules/datafile-manager/index.node.ts rename to lib/utils/type.ts index 4015d3adf..9c9a704dc 100644 --- a/lib/modules/datafile-manager/index.node.ts +++ b/lib/utils/type.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,12 @@ * limitations under the License. */ -import NodeDatafileManager from './nodeDatafileManager'; -export * from './datafileManager'; -export { NodeDatafileManager as HttpPollingDatafileManager }; -export default { HttpPollingDatafileManager: NodeDatafileManager }; +export type Fn = () => void; +export type AsyncTransformer<A, B> = (arg: A) => Promise<B>; +export type Transformer<A, B> = (arg: A) => B; + +export type Consumer<T> = (arg: T) => void; +export type AsyncComsumer<T> = (arg: T) => Promise<void>; + +export type Producer<T> = () => T; +export type AsyncProducer<T> = () => Promise<T>; diff --git a/tests/backoffController.spec.ts b/tests/backoffController.spec.ts deleted file mode 100644 index 846ac0c52..000000000 --- a/tests/backoffController.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, it, expect } from 'vitest'; - -import BackoffController from '../lib/modules/datafile-manager/backoffController'; - -describe('backoffController', () => { - describe('getDelay', () => { - it('returns 0 from getDelay if there have been no errors', () => { - const controller = new BackoffController(); - expect(controller.getDelay()).toBe(0); - }); - - it('increases the delay returned from getDelay (up to a maximum value) after each call to countError', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(8000); - expect(controller.getDelay()).toBeLessThan(9000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(16000); - expect(controller.getDelay()).toBeLessThan(17000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(32000); - expect(controller.getDelay()).toBeLessThan(33000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(64000); - expect(controller.getDelay()).toBeLessThan(65000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(128000); - expect(controller.getDelay()).toBeLessThan(129000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(256000); - expect(controller.getDelay()).toBeLessThan(257000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - // Maximum reached - additional errors should not increase the delay further - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - }); - - it('resets the error count when reset is called', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThan(0); - controller.reset(); - expect(controller.getDelay()).toBe(0); - }); - }); -}); diff --git a/tests/browserDatafileManager.spec.ts b/tests/browserDatafileManager.spec.ts deleted file mode 100644 index d643b2cb3..000000000 --- a/tests/browserDatafileManager.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, afterEach, it, expect, vi, MockInstance } from 'vitest'; - -import BrowserDatafileManager from '../lib/modules/datafile-manager/browserDatafileManager'; -import * as browserRequest from '../lib/modules/datafile-manager/browserRequest'; -import { Headers, AbortableRequest } from '../lib/modules/datafile-manager/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('browserDatafileManager', () => { - let makeGetRequestSpy: MockInstance<(reqUrl: string, headers: Headers) => AbortableRequest>; - beforeEach(() => { - vi.useFakeTimers(); - makeGetRequestSpy = vi.spyOn(browserRequest, 'makeGetRequest'); - }); - - afterEach(() => { - vi.restoreAllMocks(); - vi.clearAllTimers(); - }); - - it('calls makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to false for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should not set a timeout for a later update - expect(getTimerCount()).toBe(0); - - await manager.stop(); - }); -}); diff --git a/tests/browserRequest.spec.ts b/tests/browserRequest.spec.ts deleted file mode 100644 index 42a52329f..000000000 --- a/tests/browserRequest.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @jest-environment jsdom - */ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest'; - -import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; -import { makeGetRequest } from '../lib/modules/datafile-manager/browserRequest'; - -describe('browserRequest', () => { - describe('makeGetRequest', () => { - let mockXHR: FakeXMLHttpRequestStatic; - let xhrs: FakeXMLHttpRequest[]; - beforeEach(() => { - xhrs = []; - mockXHR = fakeXhr.useFakeXMLHttpRequest(); - mockXHR.onCreate = (req): number => xhrs.push(req); - }); - - afterEach(() => { - mockXHR.restore(); - }); - - it('makes a GET request to the argument URL', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - expect(xhrs.length).toBe(1); - const xhr = xhrs[0]; - const { url, method } = xhr; - expect({ url, method }).toEqual({ - url: 'https://cdn.optimizely.com/datafiles/123.json', - method: 'GET', - }); - - xhr.respond(200, {}, '{"foo":"bar"}'); - - await req.responsePromise; - }); - - it('returns a 200 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(200, {}, '{"foo":"bar"}'); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - headers: {}, - body: '{"foo":"bar"}', - }); - }); - - it('returns a 404 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(404, {}, ''); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - headers: {}, - body: '', - }); - }); - - it('includes headers from the headers argument in the request', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/dataifles/123.json', { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - - expect(xhrs.length).toBe(1); - expect(xhrs[0].requestHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:18 GMT'); - - xhrs[0].respond(404, {}, ''); - - await req.responsePromise; - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond( - 200, - { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - '{"foo":"bar"}' - ); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - }); - - it('returns a rejected promise when there is a request error', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - xhrs[0].error(); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('sets a timeout on the request object', () => { - const onCreateMock = vi.fn(); - mockXHR.onCreate = onCreateMock; - makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - expect(onCreateMock).toBeCalledTimes(1); - expect(onCreateMock.mock.calls[0][0].timeout).toBe(60000); - }); - }); -}); diff --git a/tests/browserRequestHandler.spec.ts b/tests/browserRequestHandler.spec.ts index 763bba54e..f28ee1f26 100644 --- a/tests/browserRequestHandler.spec.ts +++ b/tests/browserRequestHandler.spec.ts @@ -34,7 +34,7 @@ describe('BrowserRequestHandler', () => { xhrs = []; mockXHR = fakeXhr.useFakeXMLHttpRequest(); mockXHR.onCreate = (request): number => xhrs.push(request); - browserRequestHandler = new BrowserRequestHandler(new NoOpLogger()); + browserRequestHandler = new BrowserRequestHandler({ logger: new NoOpLogger() }); }); afterEach(() => { @@ -135,7 +135,7 @@ describe('BrowserRequestHandler', () => { const onCreateMock = vi.fn(); mockXHR.onCreate = onCreateMock; - new BrowserRequestHandler(new NoOpLogger(), timeout).makeRequest(host, {}, 'get'); + new BrowserRequestHandler({ logger: new NoOpLogger(), timeout }).makeRequest(host, {}, 'get'); expect(onCreateMock).toBeCalledTimes(1); expect(onCreateMock.mock.calls[0][0].timeout).toBe(timeout); diff --git a/tests/eventEmitter.spec.ts b/tests/eventEmitter.spec.ts deleted file mode 100644 index 16e91b83e..000000000 --- a/tests/eventEmitter.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { expect, vi, it, beforeEach, describe } from 'vitest'; -import EventEmitter from '../lib/modules/datafile-manager/eventEmitter'; - -describe('event_emitter', () => { - describe('on', () => { - let emitter: EventEmitter; - beforeEach(() => { - emitter = new EventEmitter(); - }); - - it('can add a listener for the update event', () => { - const listener = vi.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledTimes(1); - }); - - it('passes the argument from emit to the listener', () => { - const listener = vi.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledWith({ datafile: 'abcd' }); - }); - - it('returns a dispose function that removes the listener', () => { - const listener = vi.fn(); - const disposer = emitter.on('update', listener); - disposer(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener).toBeCalledTimes(0); - }); - - it('can add several listeners for the update event', () => { - const listener1 = vi.fn(); - const listener2 = vi.fn(); - const listener3 = vi.fn(); - emitter.on('update', listener1); - emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - }); - - it('can add several listeners and remove only some of them', () => { - const listener1 = vi.fn(); - const listener2 = vi.fn(); - const listener3 = vi.fn(); - const disposer1 = emitter.on('update', listener1); - const disposer2 = emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - disposer1(); - disposer2(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(2); - }); - - it('can add listeners for different events and remove only some of them', () => { - const readyListener = vi.fn(); - const updateListener = vi.fn(); - const readyDisposer = emitter.on('ready', readyListener); - const updateDisposer = emitter.on('update', updateListener); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(0); - emitter.emit('update', { datafile: 'abcd' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - readyDisposer(); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - emitter.emit('update', { datafile: 'efgh' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - updateDisposer(); - emitter.emit('update', { datafile: 'ijkl' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - }); - - it('can remove all listeners', () => { - const readyListener = vi.fn(); - const updateListener = vi.fn(); - emitter.on('ready', readyListener); - emitter.on('update', updateListener); - emitter.removeAllListeners(); - emitter.emit('update', { datafile: 'abcd' }); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(0); - expect(updateListener).toBeCalledTimes(0); - }); - }); -}); diff --git a/tests/httpPollingDatafileManager.spec.ts b/tests/httpPollingDatafileManager.spec.ts deleted file mode 100644 index 201fe0eae..000000000 --- a/tests/httpPollingDatafileManager.spec.ts +++ /dev/null @@ -1,744 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, beforeAll, it, expect, vi, MockInstance } from 'vitest'; - -import HttpPollingDatafileManager from '../lib/modules/datafile-manager/httpPollingDatafileManager'; -import { Headers, AbortableRequest, Response } from '../lib/modules/datafile-manager/http'; -import { DatafileManagerConfig } from '../lib/modules/datafile-manager/datafileManager'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; -import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; - - -vi.mock('../lib/modules/datafile-manager/backoffController', () => { - const MockBackoffController = vi.fn(); - MockBackoffController.prototype.getDelay = vi.fn().mockImplementation(() => 0); - MockBackoffController.prototype.countError = vi.fn(); - MockBackoffController.prototype.reset = vi.fn(); - - return { - 'default': MockBackoffController, - } -}); - - -import BackoffController from '../lib/modules/datafile-manager/backoffController'; -import { LoggerFacade, getLogger } from '../lib/modules/logging'; -import { resetCalls, spy, verify } from 'ts-mockito'; - -// Test implementation: -// - Does not make any real requests: just resolves with queued responses (tests push onto queuedResponses) -export class TestDatafileManager extends HttpPollingDatafileManager { - queuedResponses: (Response | Error)[] = []; - - responsePromises: Promise<Response>[] = []; - - simulateResponseDelay = false; - - // Need these unsued vars for the mock call types to work (being able to check calls) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - makeGetRequest(url: string, headers: Headers): AbortableRequest { - const nextResponse: Error | Response | undefined = this.queuedResponses.pop(); - let responsePromise: Promise<Response>; - if (nextResponse === undefined) { - responsePromise = Promise.reject('No responses queued'); - } else if (nextResponse instanceof Error) { - responsePromise = Promise.reject(nextResponse); - } else { - if (this.simulateResponseDelay) { - // Actual response will have some delay. This is required to get expected behavior for caching. - responsePromise = new Promise(resolve => setTimeout(() => resolve(nextResponse), 50)); - } else { - responsePromise = Promise.resolve(nextResponse); - } - } - this.responsePromises.push(responsePromise); - return { responsePromise, abort: vi.fn() }; - } - - getConfigDefaults(): Partial<DatafileManagerConfig> { - return {}; - } -} - -const testCache: PersistentKeyValueCache = { - get(key: string): Promise<string | undefined> { - let val = undefined; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<boolean> { - return Promise.resolve(false); - }, -}; - -describe('httpPollingDatafileManager', () => { - - let spiedLogger: LoggerFacade; - - const loggerName = 'DatafileManager'; - - beforeAll(() => { - const actualLogger = getLogger(loggerName); - spiedLogger = spy(actualLogger); - }); - - beforeEach(() => { - vi.useFakeTimers(); - resetCalls(spiedLogger); - }); - - let manager: TestDatafileManager; - afterEach(async () => { - if (manager) { - manager.stop(); - } - vi.clearAllMocks(); - vi.restoreAllMocks(); - vi.clearAllTimers(); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: true,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ datafile: JSON.stringify({ foo: 'abcd' }), sdkKey: '123', autoUpdate: true }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself, and updates itself again after a timeout', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"fooz": "barz"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - const updateFn = vi.fn(); - manager.on('update', updateFn); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - updateFn.mockReset(); - - await advanceTimersByTime(300000); - - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"fooz": "barz"}' }); - expect(JSON.parse(manager.get())).toEqual({ fooz: 'barz' }); - }); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: false,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - datafile: JSON.stringify({ foo: 'abcd' }), - sdkKey: '123', - autoUpdate: false, - }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself once, but does not schedule a future update', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(getTimerCount()).toBe(0); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: true', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 1000, autoUpdate: true }); - }); - - it('logs an error if fetching datafile fails', async () => { - manager.queuedResponses.push( - { - statusCode: 500, - body: '', - headers: {}, - } - ); - - manager.start(); - await advanceTimersByTime(1000); - await manager.responsePromises[0]; - - verify(spiedLogger.error('Datafile fetch request failed with status: 500')).once(); - }); - - describe('initial state', () => { - it('returns null from get before becoming ready', () => { - expect(manager.get()).toEqual(''); - }); - }); - - describe('started state', () => { - it('passes the default datafile URL to the makeGetRequest method', async () => { - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/123.json'); - await manager.onReady(); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('live updates', () => { - it('sets a timeout to update again after the update interval', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo4": "bar4"}', - headers: {}, - } - ); - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await manager.responsePromises[0]; - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('emits update events after live updates', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - const updateFn = vi.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - - await advanceTimersByTime(1000); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo2": "bar2"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo2: 'bar2' }); - - updateFn.mockReset(); - - await advanceTimersByTime(1000); - await manager.responsePromises[2]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo3": "bar3"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo3: 'bar3' }); - }); - - describe('when the update interval time fires before the request is complete', () => { - it('waits until the request is complete before making the next request', async () => { - let resolveResponsePromise: (resp: Response) => void; - const responsePromise: Promise<Response> = new Promise(res => { - resolveResponsePromise = res; - }); - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest').mockReturnValueOnce({ - abort() {}, - responsePromise, - }); - - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - resolveResponsePromise!({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - await responsePromise; - await advanceTimersByTime(0); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - it('cancels a pending timeout when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - - expect(getTimerCount()).toBe(1); - manager.stop(); - expect(getTimerCount()).toBe(0); - }); - - it('cancels reactions to a pending fetch when stop is called', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - - await advanceTimersByTime(1000); - - expect(manager.responsePromises.length).toBe(2); - manager.stop(); - await manager.responsePromises[1]; - // Should not have updated datafile since manager was stopped - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('calls abort on the current request if there is a current request when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }); - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); - manager.start(); - const currentRequest = makeGetRequestSpy.mock.results[0]; - // @ts-ignore - expect(currentRequest.type).toBe('return'); - expect(currentRequest.value.abort).toBeCalledTimes(0); - manager.stop(); - expect(currentRequest.value.abort).toBeCalledTimes(1); - }); - - it('can fail to become ready on the initial request, but succeed after a later polling update', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }, - { - statusCode: 404, - body: '', - headers: {}, - } - ); - - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - // Not ready yet due to first request failed, but should have queued a live update - expect(getTimerCount()).toBe(1); - // Trigger the update, should fetch the next response which should succeed, then we get ready - advanceTimersByTime(1000); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('newness checking', () => { - it('does not update if the response status is 304', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - - const updateFn = vi.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - // First response promise was for the initial 200 response - expect(manager.responsePromises.length).toBe(1); - // Trigger the queued update - await advanceTimersByTime(1000); - // Second response promise is for the 304 response - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - // Since the response was 304, updateFn should not have been called - expect(updateFn).toBeCalledTimes(0); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('sends if-modified-since using the last observed response last-modified', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - manager.start(); - await manager.onReady(); - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - const firstCall = makeGetRequestSpy.mock.calls[0]; - const headers = firstCall[1]; - expect(headers).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - }); - }); - - describe('backoff', () => { - it('uses the delay from the backoff controller getDelay method when greater than updateInterval', async () => { - const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; - const getDelayMock = BackoffControllerMock.mock.results[0].value.getDelay; - getDelayMock.mockImplementationOnce(() => 5432); - - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // Should not make another request after 1 second because the error should have triggered backoff - advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // But after another 5 seconds, another request should be made - await advanceTimersByTime(5000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('calls countError on the backoff controller when a non-success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls countError on the backoff controller when the response promise rejects', async () => { - manager.queuedResponses.push(new Error('Connection failed')); - manager.start(); - try { - await manager.responsePromises[0]; - } catch (e) { - //empty - } - const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls reset on the backoff controller when a success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }); - manager.start(); - const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; - // Reset is called in start - we want to check that it is also called after the response, so reset the mock here - BackoffControllerMock.mock.results[0].value.reset.mockReset(); - await manager.onReady(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - }); - - it('resets the backoff controller when start is called', async () => { - const BackoffControllerMock = (BackoffController as unknown) as MockInstance<() => BackoffController>; - manager.start(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - try { - await manager.responsePromises[0]; - } catch (e) { - // empty - } - }); - }); - }); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: false', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', autoUpdate: false }); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('does not schedule a live update after ready', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - const updateFn = vi.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(getTimerCount()).toBe(0); - }); - - // TODO: figure out what's wrong with this test - it.skip('rejects the onReady promise if the initial request promise rejects', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.makeGetRequest = (): AbortableRequest => ({ - abort(): void {}, - responsePromise: Promise.reject(new Error('Could not connect')), - }); - manager.start(); - let didReject = false; - try { - await manager.onReady(); - } catch (e) { - didReject = true; - } - expect(didReject).toBe(true); - }); - }); - - describe('when constructed with sdkKey and a valid urlTemplate', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: '456', - updateInterval: 1000, - urlTemplate: 'https://localhost:5556/datafiles/%s', - }); - }); - - it('uses the urlTemplate to create the url passed to the makeGetRequest method', async () => { - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://localhost:5556/datafiles/456'); - await manager.onReady(); - }); - }); - - describe('when constructed with an update interval below the minimum', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 500, autoUpdate: true }); - }); - - it('uses the default update interval', async () => { - const makeGetRequestSpy = vi.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - describe('when constructed with a cache implementation having an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('uses cached version of datafile first and resolves the promise while network throws error and no update event is triggered', async () => { - manager.queuedResponses.push(new Error('Connection Error')); - const updateFn = vi.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - }); - - it('uses cached datafile, resolves ready promise, fetches new datafile from network and triggers update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = vi.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(1); - }); - - it('sets newly recieved datafile in to cache', async () => { - const cacheSetSpy = vi.spyOn(testCache, 'set'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(cacheSetSpy.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); - expect(JSON.parse(cacheSetSpy.mock.calls[0][1])).toEqual({ foo: 'bar' }); - }); - }); - - describe('when constructed with a cache implementation without an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatDoesExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('does not find cached datafile, fetches new datafile from network, resolves promise and does not trigger update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = vi.fn(); - manager.on('update', updateFn); - manager.start(); - await advanceTimersByTime(50); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - }); - }); -}); diff --git a/tests/httpPollingDatafileManagerPolling.spec.ts b/tests/httpPollingDatafileManagerPolling.spec.ts deleted file mode 100644 index f1e57b864..000000000 --- a/tests/httpPollingDatafileManagerPolling.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2023-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, beforeAll, it, expect, vi, MockInstance } from 'vitest'; - -import { resetCalls, spy, verify } from 'ts-mockito'; -import { LogLevel, LoggerFacade, getLogger, setLogLevel } from '../lib/modules/logging'; -import { UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from '../lib/modules/datafile-manager/config'; -import { TestDatafileManager } from './httpPollingDatafileManager.spec'; - -describe('HttpPollingDatafileManager polling', () => { - let spiedLogger: LoggerFacade; - - const loggerName = 'DatafileManager'; - const sdkKey = 'not-real-sdk'; - - beforeAll(() => { - setLogLevel(LogLevel.DEBUG); - const actualLogger = getLogger(loggerName); - spiedLogger = spy(actualLogger); - }); - - beforeEach(() => { - resetCalls(spiedLogger); - }); - - - it('should log polling interval below 30 seconds', () => { - const below30Seconds = 29 * 1000; - - new TestDatafileManager({ - sdkKey, - updateInterval: below30Seconds, - }); - - - verify(spiedLogger.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE)).once(); - }); - - it('should not log when polling interval above 30s', () => { - const oneMinute = 60 * 1000; - - new TestDatafileManager({ - sdkKey, - updateInterval: oneMinute, - }); - - verify(spiedLogger.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE)).never(); - }); -}); diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 425b4d1cb..32408ee6f 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -24,6 +24,8 @@ import packageJSON from '../package.json'; import optimizelyFactory from '../lib/index.react_native'; import configValidator from '../lib/utils/config_validator'; import eventProcessorConfigValidator from '../lib/utils/event_processor_config_validator'; +import { getMockProjectConfigManager } from '../lib/tests/mock/mock_project_config_manager'; +import { createProjectConfig } from '../lib/project_config/project_config'; vi.mock('@react-native-community/netinfo'); vi.mock('react-native-get-random-values') @@ -71,27 +73,21 @@ describe('javascript-sdk/react-native', () => { it('should not throw if the provided config is not valid', () => { expect(function() { const optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), // @ts-ignore logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function() {}); }).not.toThrow(); }); it('should create an instance of optimizely', () => { const optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function() {}); expect(optlyInstance).toBeInstanceOf(Optimizely); // @ts-ignore @@ -100,15 +96,13 @@ describe('javascript-sdk/react-native', () => { it('should set the React Native JS client engine and javascript SDK version', () => { const optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function() {}); + // @ts-ignore expect('react-native-js-sdk').toEqual(optlyInstance.clientEngine); // @ts-ignore @@ -118,15 +112,12 @@ describe('javascript-sdk/react-native', () => { it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { const optlyInstance = optimizelyFactory.createInstance({ clientEngine: 'react-sdk', - datafile: {}, + projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function() {}); // @ts-ignore expect('react-native-sdk').toEqual(optlyInstance.clientEngine); }); @@ -142,7 +133,9 @@ describe('javascript-sdk/react-native', () => { it('should call logging.setLogLevel', () => { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, }); expect(logging.setLogLevel).toBeCalledTimes(1); @@ -162,7 +155,9 @@ describe('javascript-sdk/react-native', () => { it('should call logging.setLogHandler with the supplied logger', () => { const fakeLogger = { log: function() {} }; optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), // @ts-ignore logger: fakeLogger, }); @@ -184,7 +179,9 @@ describe('javascript-sdk/react-native', () => { it('should use default event flush interval when none is provided', () => { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore @@ -212,7 +209,9 @@ describe('javascript-sdk/react-native', () => { it('should ignore the event flush interval and use the default instead', () => { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore @@ -242,7 +241,9 @@ describe('javascript-sdk/react-native', () => { it('should use the provided event flush interval', () => { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore @@ -262,7 +263,9 @@ describe('javascript-sdk/react-native', () => { it('should use default event batch size when none is provided', () => { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore @@ -319,7 +322,9 @@ describe('javascript-sdk/react-native', () => { it('should use the provided event batch size', () => { optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, // @ts-ignore diff --git a/tests/nodeDatafileManager.spec.ts b/tests/nodeDatafileManager.spec.ts deleted file mode 100644 index 11217663c..000000000 --- a/tests/nodeDatafileManager.spec.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, beforeAll, it, expect, vi, MockInstance } from 'vitest'; - -import NodeDatafileManager from '../lib/modules/datafile-manager/nodeDatafileManager'; -import * as nodeRequest from '../lib/modules/datafile-manager/nodeRequest'; -import { Headers, AbortableRequest } from '../lib/modules/datafile-manager/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('nodeDatafileManager', () => { - let makeGetRequestSpy: MockInstance<(reqUrl: string, headers: Headers) => AbortableRequest>; - beforeEach(() => { - vi.useFakeTimers(); - makeGetRequestSpy = vi.spyOn(nodeRequest, 'makeGetRequest'); - }); - - afterEach(() => { - vi.restoreAllMocks(); - vi.clearAllTimers(); - }); - - it('calls nodeEnvironment.makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls nodeEnvironment.makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to true for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should set a timeout for a later update - expect(getTimerCount()).toBe(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - - await manager.stop(); - }); - - it('uses authenticated default datafile url when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith( - 'https://config.optimizely.com/datafiles/auth/1234.json', - expect.anything() - ); - await manager.stop(); - }); - - it('uses public default datafile url when auth token is not provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://cdn.optimizely.com/datafiles/1234.json', expect.anything()); - await manager.stop(); - }); - - it('adds authorization header with bearer token when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith(expect.anything(), { Authorization: 'Bearer abcdefgh' }); - await manager.stop(); - }); - - it('prefers user provided url template over defaults', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: vi.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - urlTemplate: 'https://myawesomeurl/', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://myawesomeurl/', expect.anything()); - await manager.stop(); - }); -}); diff --git a/tests/nodeRequest.spec.ts b/tests/nodeRequest.spec.ts deleted file mode 100644 index 8f1c66c8e..000000000 --- a/tests/nodeRequest.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, beforeAll, afterAll, it, vi, expect } from 'vitest'; - -import nock from 'nock'; -import zlib from 'zlib'; -import { makeGetRequest } from '../lib/modules/datafile-manager/nodeRequest'; -import { advanceTimersByTime } from './testUtils'; - -beforeAll(() => { - nock.disableNetConnect(); -}); - -afterAll(() => { - nock.enableNetConnect(); -}); - -describe('nodeEnvironment', () => { - const host = 'https://cdn.optimizely.com/'; - const path = '/datafiles/123.json'; - - afterEach(async () => { - nock.cleanAll(); - }); - - describe('makeGetRequest', () => { - it('returns a 200 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a 404 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(404, ''); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('includes headers from the headers argument in the request', async () => { - const scope = nock(host) - .matchHeader('if-modified-since', 'Fri, 08 Mar 2019 18:57:18 GMT') - .get(path) - .reply(304, ''); - const req = makeGetRequest(`${host}${path}`, { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 304, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('adds an Accept-Encoding request header and unzips a gzipped response body', async () => { - const scope = nock(host) - .matchHeader('accept-encoding', 'gzip,deflate') - .get(path) - .reply(200, () => zlib.gzipSync('{"foo":"bar"}'), { 'content-encoding': 'gzip' }); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toMatchObject({ - statusCode: 200, - body: '{"foo":"bar"}', - }); - scope.done(); - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const scope = await nock(host) - .get(path) - .reply( - 200, - { foo: 'bar' }, - { - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - } - ); - const req = await makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - scope.done(); - }); - - it('handles a URL with a query string', async () => { - const pathWithQuery = '/datafiles/123.json?from_my_app=true'; - const scope = nock(host) - .get(pathWithQuery) - .reply(200, { foo: 'bar' }); - const req = makeGetRequest(`${host}${pathWithQuery}`, {}); - await req.responsePromise; - scope.done(); - }); - - it('handles a URL with http protocol (not https)', async () => { - const httpHost = 'http://cdn.optimizely.com/'; - const scope = nock(httpHost) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${httpHost}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a rejected response promise when the URL protocol is unsupported', async () => { - const invalidProtocolUrl = 'ftp://something/datafiles/123.json'; - const req = makeGetRequest(invalidProtocolUrl, {}); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('returns a rejected promise when there is a request error', async () => { - const scope = nock(host) - .get(path) - .replyWithError({ - message: 'Connection error', - code: 'CONNECTION_ERROR', - }); - const req = makeGetRequest(`${host}${path}`, {}); - await expect(req.responsePromise).rejects.toThrow(); - scope.done(); - }); - - it('handles a url with a host and a port', async () => { - const hostWithPort = 'http://datafiles:3000/'; - const path = '/12/345.json'; - const scope = nock(hostWithPort) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${hostWithPort}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - describe('timeout', () => { - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.clearAllTimers(); - }); - - it('rejects the response promise and aborts the request when the response is not received before the timeout', async () => { - const scope = nock(host) - .get(path) - .delay(61000) - .reply(200, '{"foo":"bar"}'); - - const abortEventListener = vi.fn(); - let emittedReq: any; - const requestListener = (request: any): void => { - emittedReq = request; - emittedReq.once('abort', abortEventListener); - }; - scope.on('request', requestListener); - - const req = makeGetRequest(`${host}${path}`, {}); - await advanceTimersByTime(60000); - await expect(req.responsePromise).rejects.toThrow(); - expect(abortEventListener).toBeCalledTimes(1); - - scope.done(); - if (emittedReq) { - emittedReq.off('abort', abortEventListener); - } - scope.off('request', requestListener); - }); - }); - }); -}); diff --git a/tests/nodeRequestHandler.spec.ts b/tests/nodeRequestHandler.spec.ts index 06c2e2bac..9bcc0d813 100644 --- a/tests/nodeRequestHandler.spec.ts +++ b/tests/nodeRequestHandler.spec.ts @@ -37,7 +37,7 @@ describe('NodeRequestHandler', () => { let nodeRequestHandler: NodeRequestHandler; beforeEach(() => { - nodeRequestHandler = new NodeRequestHandler(new NoOpLogger()); + nodeRequestHandler = new NodeRequestHandler({ logger: new NoOpLogger() }); }); afterEach(async () => { @@ -218,7 +218,7 @@ describe('NodeRequestHandler', () => { }; scope.on('request', requestListener); - const request = new NodeRequestHandler(new NoOpLogger(), 100).makeRequest(`${host}${path}`, {}, 'get'); + const request = new NodeRequestHandler({ logger: new NoOpLogger(), timeout: 100 }).makeRequest(`${host}${path}`, {}, 'get'); vi.advanceTimersByTime(60000); vi.runAllTimers(); // <- explicitly tell vi to run all setTimeout, setInterval diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index 2622b5c4d..89ecc8030 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -196,7 +196,7 @@ describe('OdpManager', () => { it('Custom odpOptions.segmentsRequestHandler overrides default Segment API Request Handler', () => { const odpOptions: OdpOptions = { - segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), + segmentsRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 4000 }), }; const browserOdpManager = BrowserOdpManager.createInstance({ @@ -210,7 +210,7 @@ describe('OdpManager', () => { it('Custom odpOptions.segmentRequestHandler override takes precedence over odpOptions.eventApiTimeout', () => { const odpOptions: OdpOptions = { segmentsApiTimeout: 2, - segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 1), + segmentsRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), }; const browserOdpManager = BrowserOdpManager.createInstance({ @@ -247,7 +247,7 @@ describe('OdpManager', () => { maxSize: 1, timeout: 1, }), - new OdpSegmentApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger), + new OdpSegmentApiManager(new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), fakeLogger), fakeLogger, odpConfig, ); @@ -257,7 +257,7 @@ describe('OdpManager', () => { segmentsCacheTimeout: 2, segmentsCache: new BrowserLRUCache<string, string[]>({ maxSize: 2, timeout: 2 }), segmentsApiTimeout: 2, - segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 2), + segmentsRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 2 }), segmentManager: customSegmentManager, }; @@ -370,7 +370,7 @@ describe('OdpManager', () => { it('Custom odpOptions.eventRequestHandler overrides default Event Manager request handler', () => { const odpOptions: OdpOptions = { - eventRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), + eventRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 4000 }), }; const browserOdpManager = BrowserOdpManager.createInstance({ @@ -387,7 +387,7 @@ describe('OdpManager', () => { eventBatchSize: 2, eventFlushInterval: 2, eventQueueSize: 2, - eventRequestHandler: new BrowserRequestHandler(fakeLogger, 1), + eventRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), }; const browserOdpManager = BrowserOdpManager.createInstance({ @@ -434,7 +434,7 @@ describe('OdpManager', () => { const customEventManager = new BrowserOdpEventManager({ odpConfig, - apiManager: new BrowserOdpEventApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger), + apiManager: new BrowserOdpEventApiManager(new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), fakeLogger), logger: fakeLogger, clientEngine: fakeClientEngine, clientVersion: fakeClientVersion, @@ -448,7 +448,7 @@ describe('OdpManager', () => { eventBatchSize: 2, eventFlushInterval: 2, eventQueueSize: 2, - eventRequestHandler: new BrowserRequestHandler(fakeLogger, 3), + eventRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 3 }), eventManager: customEventManager, }; diff --git a/tests/reactNativeDatafileManager.spec.ts b/tests/reactNativeDatafileManager.spec.ts deleted file mode 100644 index 2a3c354f4..000000000 --- a/tests/reactNativeDatafileManager.spec.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, afterEach, it, vi, expect, MockedObject } from 'vitest'; - -const { mockMap, mockGet, mockSet, mockRemove, mockContains } = vi.hoisted(() => { - const mockMap = new Map(); - - const mockGet = vi.fn().mockImplementation((key) => { - return Promise.resolve(mockMap.get(key)); - }); - - const mockSet = vi.fn().mockImplementation((key, value) => { - mockMap.set(key, value); - return Promise.resolve(); - }); - - const mockRemove = vi.fn().mockImplementation((key) => { - if (mockMap.has(key)) { - mockMap.delete(key); - return Promise.resolve(true); - } - return Promise.resolve(false); - }); - - const mockContains = vi.fn().mockImplementation((key) => { - return Promise.resolve(mockMap.has(key)); - }); - - return { mockMap, mockGet, mockSet, mockRemove, mockContains }; -}); - -vi.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { - const MockReactNativeAsyncStorageCache = vi.fn(); - MockReactNativeAsyncStorageCache.prototype.get = mockGet; - MockReactNativeAsyncStorageCache.prototype.set = mockSet; - MockReactNativeAsyncStorageCache.prototype.contains = mockContains; - MockReactNativeAsyncStorageCache.prototype.remove = mockRemove; - return { 'default': MockReactNativeAsyncStorageCache }; -}); - - -import { advanceTimersByTime } from './testUtils'; -import ReactNativeDatafileManager from '../lib/modules/datafile-manager/reactNativeDatafileManager'; -import { Headers, AbortableRequest, Response } from '../lib/modules/datafile-manager/http'; -import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; -import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; - -class MockRequestReactNativeDatafileManager extends ReactNativeDatafileManager { - queuedResponses: (Response | Error)[] = []; - - responsePromises: Promise<Response>[] = []; - - simulateResponseDelay = false; - - makeGetRequest(url: string, headers: Headers): AbortableRequest { - const nextResponse: Error | Response | undefined = this.queuedResponses.pop(); - let responsePromise: Promise<Response>; - if (nextResponse === undefined) { - responsePromise = Promise.reject('No responses queued'); - } else if (nextResponse instanceof Error) { - responsePromise = Promise.reject(nextResponse); - } else { - if (this.simulateResponseDelay) { - // Actual response will have some delay. This is required to get expected behavior for caching. - responsePromise = new Promise(resolve => setTimeout(() => resolve(nextResponse), 50)); - } else { - responsePromise = Promise.resolve(nextResponse); - } - } - this.responsePromises.push(responsePromise); - return { responsePromise, abort: vi.fn() }; - } -} - -describe('reactNativeDatafileManager', () => { - const MockedReactNativeAsyncStorageCache = vi.mocked(ReactNativeAsyncStorageCache); - - const testCache: PersistentKeyValueCache = { - get(key: string): Promise<string | undefined> { - let val = undefined; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<boolean> { - return Promise.resolve(false); - }, - }; - - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.clearAllTimers(); - vi.useRealTimers(); - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockSet.mockClear(); - mockContains.mockClear(); - mockRemove.mockClear(); - }); - - it('uses the user provided cache', async () => { - const setSpy = vi.spyOn(testCache, 'set'); - - const manager = new MockRequestReactNativeDatafileManager({ - sdkKey: 'keyThatExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - - manager.simulateResponseDelay = true; - - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - manager.start(); - vi.advanceTimersByTime(50); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(setSpy.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); - expect(JSON.parse(setSpy.mock.calls[0][1])).toEqual({ foo: 'bar' }); - }); - - it('uses ReactNativeAsyncStorageCache if no cache is provided', async () => { - const manager = new MockRequestReactNativeDatafileManager({ - sdkKey: 'keyThatExists', - updateInterval: 500, - autoUpdate: true - }); - manager.simulateResponseDelay = true; - - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - manager.start(); - vi.advanceTimersByTime(50); - await manager.onReady(); - - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(mockSet.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); - expect(JSON.parse(mockSet.mock.calls[0][1] as string)).toEqual({ foo: 'bar' }); - }); -}); diff --git a/tests/reactNativeHttpPollingDatafileManager.spec.ts b/tests/reactNativeHttpPollingDatafileManager.spec.ts deleted file mode 100644 index 466efdb43..000000000 --- a/tests/reactNativeHttpPollingDatafileManager.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, it, vi, expect } from 'vitest'; - -vi.mock('../lib/modules/datafile-manager/index.react_native', () => { - return { - HttpPollingDatafileManager: vi.fn().mockImplementation(() => { - return { - get(): string { - return '{}'; - }, - on(): (() => void) { - return () => {}; - }, - onReady(): Promise<void> { - return Promise.resolve(); - }, - }; - }), - } -}); - -import { HttpPollingDatafileManager } from '../lib/modules/datafile-manager/index.react_native'; -import { createHttpPollingDatafileManager } from '../lib/plugins/datafile_manager/react_native_http_polling_datafile_manager'; -import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; -import { PersistentCacheProvider } from '../lib/shared_types'; - -describe('createHttpPollingDatafileManager', () => { - const MockedHttpPollingDatafileManager = vi.mocked(HttpPollingDatafileManager); - - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - vi.clearAllTimers(); - MockedHttpPollingDatafileManager.mockClear(); - }); - - it('calls the provided persistentCacheFactory and passes it to the HttpPollingDatafileManagerConstructor', async () => { - const fakePersistentCache : PersistentKeyValueCache = { - contains(k: string): Promise<boolean> { - return Promise.resolve(false); - }, - get(key: string): Promise<string | undefined> { - return Promise.resolve(undefined); - }, - remove(key: string): Promise<boolean> { - return Promise.resolve(false); - }, - set(key: string, val: string): Promise<void> { - return Promise.resolve() - } - } - - const fakePersistentCacheProvider = vi.fn().mockImplementation(() => { - return fakePersistentCache; - }); - - const noop = () => {}; - - createHttpPollingDatafileManager( - 'test-key', - { log: noop, info: noop, debug: noop, error: noop, warn: noop }, - undefined, - {}, - fakePersistentCacheProvider, - ) - - expect(MockedHttpPollingDatafileManager).toHaveBeenCalledTimes(1); - - const { cache } = MockedHttpPollingDatafileManager.mock.calls[0][0]; - expect(cache === fakePersistentCache).toBeTruthy(); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index cd3b58451..ef8012773 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,6 +29,7 @@ "./dist", "./lib/**/*.tests.js", "./lib/**/*.tests.ts", + "./lib/**/*.spec.ts", "./lib/**/*.umdtests.js", "./lib/tests", "node_modules" diff --git a/vitest.config.mts b/vitest.config.mts index d74a1e1fd..673f7d1c6 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { defineConfig } from 'vitest/config' export default defineConfig({ From 9e37f00137f496103304fe1aa31f14e6f6f3a7dc Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 4 Oct 2024 23:11:16 +0600 Subject: [PATCH 092/200] [FSSDK-10643] make event processor injectable (#948) --- lib/core/decision_service/index.tests.js | 6 +- lib/core/event_builder/build_event_v1.ts | 4 +- lib/core/event_builder/index.ts | 2 +- .../default_dispatcher.browser.spec.ts | 49 +++ .../default_dispatcher.browser.ts | 23 ++ .../default_dispatcher.node.spec.ts | 49 +++ .../default_dispatcher.node.ts | 22 ++ .../default_dispatcher.spec.ts | 117 ++++++ lib/event_processor/default_dispatcher.ts | 44 +++ .../event_processor/eventDispatcher.ts | 8 +- .../event_processor/eventProcessor.ts | 8 +- .../event_processor/eventQueue.ts | 4 +- .../event_processor_factory.browser.spec.ts | 55 +++ .../event_processor_factory.browser.ts | 26 ++ .../event_processor_factory.node.spec.ts | 55 +++ .../event_processor_factory.node.ts | 25 ++ ...ent_processor_factory.react_native.spec.ts | 56 +++ .../event_processor_factory.react_native.ts | 25 ++ lib/{modules => }/event_processor/events.ts | 2 +- .../forwarding_event_processor.spec.ts | 98 +++++ .../forwarding_event_processor.ts | 16 +- .../event_processor/index.react_native.ts | 2 +- lib/{modules => }/event_processor/index.ts | 2 +- lib/{modules => }/event_processor/managed.ts | 2 +- .../pendingEventsDispatcher.ts | 33 +- .../event_processor/pendingEventsStore.ts | 6 +- .../event_processor/reactNativeEventsStore.ts | 8 +- .../event_processor/requestTracker.ts | 2 +- .../event_processor/synchronizer.ts | 2 +- .../event_processor/v1/buildEventV1.ts | 2 +- .../v1/v1EventProcessor.react_native.ts | 28 +- .../event_processor/v1/v1EventProcessor.ts | 10 +- lib/index.browser.tests.js | 343 +++++++++-------- lib/index.browser.ts | 99 ++--- lib/index.lite.ts | 4 - lib/index.node.tests.js | 235 ++++++------ lib/index.node.ts | 58 +-- lib/index.react_native.ts | 55 +-- lib/optimizely/index.tests.js | 215 +++++++---- lib/optimizely/index.ts | 21 +- lib/optimizely_user_context/index.tests.js | 23 +- .../event_dispatcher/index.browser.tests.js | 89 ----- lib/plugins/event_dispatcher/index.browser.ts | 88 ----- .../event_dispatcher/index.node.tests.js | 112 ------ lib/plugins/event_dispatcher/index.node.ts | 78 ---- lib/plugins/event_dispatcher/no_op.ts | 5 +- .../send_beacon_dispatcher.ts | 14 +- .../forwarding_event_processor.tests.js | 53 --- .../event_processor/index.react_native.ts | 4 +- lib/plugins/event_processor/index.ts | 2 +- lib/shared_types.ts | 27 +- lib/tests/mock/mock_repeater.ts | 24 -- lib/utils/enums/index.ts | 31 +- lib/utils/event_tag_utils/index.ts | 4 +- package-lock.json | 6 +- tests/buildEventV1.spec.ts | 4 +- tests/eventQueue.spec.ts | 2 +- tests/index.react_native.spec.ts | 361 +++++++++--------- tests/pendingEventsDispatcher.spec.ts | 103 +++-- tests/pendingEventsStore.spec.ts | 2 +- tests/reactNativeEventsStore.spec.ts | 5 +- tests/reactNativeV1EventProcessor.spec.ts | 8 +- tests/requestTracker.spec.ts | 4 +- tests/sendBeaconDispatcher.spec.ts | 86 ++--- tests/v1EventProcessor.react_native.spec.ts | 106 ++--- tests/v1EventProcessor.spec.ts | 70 ++-- 66 files changed, 1700 insertions(+), 1432 deletions(-) create mode 100644 lib/event_processor/default_dispatcher.browser.spec.ts create mode 100644 lib/event_processor/default_dispatcher.browser.ts create mode 100644 lib/event_processor/default_dispatcher.node.spec.ts create mode 100644 lib/event_processor/default_dispatcher.node.ts create mode 100644 lib/event_processor/default_dispatcher.spec.ts create mode 100644 lib/event_processor/default_dispatcher.ts rename lib/{modules => }/event_processor/eventDispatcher.ts (78%) rename lib/{modules => }/event_processor/eventProcessor.ts (93%) rename lib/{modules => }/event_processor/eventQueue.ts (97%) create mode 100644 lib/event_processor/event_processor_factory.browser.spec.ts create mode 100644 lib/event_processor/event_processor_factory.browser.ts create mode 100644 lib/event_processor/event_processor_factory.node.spec.ts create mode 100644 lib/event_processor/event_processor_factory.node.ts create mode 100644 lib/event_processor/event_processor_factory.react_native.spec.ts create mode 100644 lib/event_processor/event_processor_factory.react_native.ts rename lib/{modules => }/event_processor/events.ts (98%) create mode 100644 lib/event_processor/forwarding_event_processor.spec.ts rename lib/{plugins => }/event_processor/forwarding_event_processor.ts (72%) rename lib/{modules => }/event_processor/index.react_native.ts (95%) rename lib/{modules => }/event_processor/index.ts (95%) rename lib/{modules => }/event_processor/managed.ts (94%) rename lib/{modules => }/event_processor/pendingEventsDispatcher.ts (73%) rename lib/{modules => }/event_processor/pendingEventsStore.ts (95%) rename lib/{modules => }/event_processor/reactNativeEventsStore.ts (90%) rename lib/{modules => }/event_processor/requestTracker.ts (98%) rename lib/{modules => }/event_processor/synchronizer.ts (97%) rename lib/{modules => }/event_processor/v1/buildEventV1.ts (99%) rename lib/{modules => }/event_processor/v1/v1EventProcessor.react_native.ts (92%) rename lib/{modules => }/event_processor/v1/v1EventProcessor.ts (91%) delete mode 100644 lib/plugins/event_dispatcher/index.browser.tests.js delete mode 100644 lib/plugins/event_dispatcher/index.browser.ts delete mode 100644 lib/plugins/event_dispatcher/index.node.tests.js delete mode 100644 lib/plugins/event_dispatcher/index.node.ts delete mode 100644 lib/plugins/event_processor/forwarding_event_processor.tests.js diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index b9197ebab..025a11d69 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -25,14 +25,14 @@ import { DECISION_SOURCES, } from '../../utils/enums'; import { createLogger } from '../../plugins/logger'; -import { createForwardingEventProcessor } from '../../plugins/event_processor/forwarding_event_processor'; +import { getForwardingEventProcessor } from '../../event_processor/forwarding_event_processor'; import { createNotificationCenter } from '../notification_center'; import Optimizely from '../../optimizely'; import OptimizelyUserContext from '../../optimizely_user_context'; import projectConfig, { createProjectConfig } from '../../project_config/project_config'; import AudienceEvaluator from '../audience_evaluator'; import errorHandler from '../../plugins/error_handler'; -import eventDispatcher from '../../plugins/event_dispatcher/index.node'; +import eventDispatcher from '../../event_processor/default_dispatcher.browser'; import * as jsonSchemaValidator from '../../utils/json_schema_validator'; import { getMockProjectConfigManager } from '../../tests/mock/mock_project_config_manager'; @@ -1075,7 +1075,7 @@ describe('lib/core/decision_service', function() { jsonSchemaValidator: jsonSchemaValidator, isValidInstance: true, logger: createdLogger, - eventProcessor: createForwardingEventProcessor(eventDispatcher), + eventProcessor: getForwardingEventProcessor(eventDispatcher), notificationCenter: createNotificationCenter(createdLogger, errorHandler), errorHandler: errorHandler, }); diff --git a/lib/core/event_builder/build_event_v1.ts b/lib/core/event_builder/build_event_v1.ts index b1f5b271d..1ca9c63ea 100644 --- a/lib/core/event_builder/build_event_v1.ts +++ b/lib/core/event_builder/build_event_v1.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021-2022, Optimizely + * Copyright 2021-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import { EventTags, ConversionEvent, ImpressionEvent, -} from '../../modules/event_processor'; +} from '../../event_processor'; import { Event } from '../../shared_types'; diff --git a/lib/core/event_builder/index.ts b/lib/core/event_builder/index.ts index f896adbea..707cb178c 100644 --- a/lib/core/event_builder/index.ts +++ b/lib/core/event_builder/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { LoggerFacade } from '../../modules/logging'; -import { EventV1 as CommonEventParams } from '../../modules/event_processor'; +import { EventV1 as CommonEventParams } from '../../event_processor'; import fns from '../../utils/fns'; import { CONTROL_ATTRIBUTES, RESERVED_EVENT_KEYWORDS } from '../../utils/enums'; diff --git a/lib/event_processor/default_dispatcher.browser.spec.ts b/lib/event_processor/default_dispatcher.browser.spec.ts new file mode 100644 index 000000000..4c35e39a7 --- /dev/null +++ b/lib/event_processor/default_dispatcher.browser.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, expect, it, describe, afterAll } from 'vitest'; + +vi.mock('./default_dispatcher', () => { + const DefaultEventDispatcher = vi.fn(); + return { DefaultEventDispatcher }; +}); + +vi.mock('../utils/http_request_handler/browser_request_handler', () => { + const BrowserRequestHandler = vi.fn(); + return { BrowserRequestHandler }; +}); + +import { DefaultEventDispatcher } from './default_dispatcher'; +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import eventDispatcher from './default_dispatcher.browser'; + +describe('eventDispatcher', () => { + afterAll(() => { + MockDefaultEventDispatcher.mockReset(); + MockBrowserRequestHandler.mockReset(); + }); + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const MockDefaultEventDispatcher = vi.mocked(DefaultEventDispatcher); + + it('creates and returns the instance by calling DefaultEventDispatcher', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + }); + + it('uses a BrowserRequestHandler', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + expect(Object.is(MockDefaultEventDispatcher.mock.calls[0][0], MockBrowserRequestHandler.mock.instances[0])).toBe(true); + }); +}); diff --git a/lib/event_processor/default_dispatcher.browser.ts b/lib/event_processor/default_dispatcher.browser.ts new file mode 100644 index 000000000..12cdf5a3e --- /dev/null +++ b/lib/event_processor/default_dispatcher.browser.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; +import { EventDispatcher } from '../event_processor'; +import { DefaultEventDispatcher } from './default_dispatcher'; + +const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new BrowserRequestHandler()); + +export default eventDispatcher; diff --git a/lib/event_processor/default_dispatcher.node.spec.ts b/lib/event_processor/default_dispatcher.node.spec.ts new file mode 100644 index 000000000..ddfc0c763 --- /dev/null +++ b/lib/event_processor/default_dispatcher.node.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vi, expect, it, describe, afterAll } from 'vitest'; + +vi.mock('./default_dispatcher', () => { + const DefaultEventDispatcher = vi.fn(); + return { DefaultEventDispatcher }; +}); + +vi.mock('../utils/http_request_handler/node_request_handler', () => { + const NodeRequestHandler = vi.fn(); + return { NodeRequestHandler }; +}); + +import { DefaultEventDispatcher } from './default_dispatcher'; +import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import eventDispatcher from './default_dispatcher.node'; + +describe('eventDispatcher', () => { + const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); + const MockDefaultEventDispatcher = vi.mocked(DefaultEventDispatcher); + + afterAll(() => { + MockDefaultEventDispatcher.mockReset(); + MockNodeRequestHandler.mockReset(); + }) + + it('creates and returns the instance by calling DefaultEventDispatcher', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + }); + + it('uses a NodeRequestHandler', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + expect(Object.is(MockDefaultEventDispatcher.mock.calls[0][0], MockNodeRequestHandler.mock.instances[0])).toBe(true); + }); +}); diff --git a/lib/event_processor/default_dispatcher.node.ts b/lib/event_processor/default_dispatcher.node.ts new file mode 100644 index 000000000..8d2cd852c --- /dev/null +++ b/lib/event_processor/default_dispatcher.node.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EventDispatcher } from '../event_processor'; +import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { DefaultEventDispatcher } from './default_dispatcher'; + +const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new NodeRequestHandler()); + +export default eventDispatcher; diff --git a/lib/event_processor/default_dispatcher.spec.ts b/lib/event_processor/default_dispatcher.spec.ts new file mode 100644 index 000000000..0616ba3bf --- /dev/null +++ b/lib/event_processor/default_dispatcher.spec.ts @@ -0,0 +1,117 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, vi, describe, it } from 'vitest'; +import { DefaultEventDispatcher } from './default_dispatcher'; +import { EventV1 } from '../event_processor'; + +const getEvent = (): EventV1 => { + return { + account_id: 'string', + project_id: 'string', + revision: 'string', + client_name: 'string', + client_version: 'string', + anonymize_ip: true, + enrich_decisions: false, + visitors: [], + }; +}; + +describe('DefaultEventDispatcher', () => { + it('reject the response promise if the eventObj.httpVerb is not POST', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'GET' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.resolve({ statusCode: 203 }), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + await expect(dispatcher.dispatchEvent(eventObj)).rejects.toThrow(); + }); + + it('sends correct headers and data to the requestHandler', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'POST' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.resolve({ statusCode: 203 }), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + await dispatcher.dispatchEvent(eventObj); + + expect(requestHnadler.makeRequest).toHaveBeenCalledWith( + eventObj.url, + { + 'content-type': 'application/json', + 'content-length': JSON.stringify(eventObj.params).length.toString(), + }, + 'POST', + JSON.stringify(eventObj.params) + ); + }); + + it('returns a promise that resolves with correct value if the response of the requestHandler resolves', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'POST' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.resolve({ statusCode: 203 }), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + const response = await dispatcher.dispatchEvent(eventObj); + + expect(response.statusCode).toEqual(203); + }); + + it('returns a promise that rejects if the response of the requestHandler rejects', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'POST' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.reject(new Error('error')), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + await expect(dispatcher.dispatchEvent(eventObj)).rejects.toThrow(); + }); +}); diff --git a/lib/event_processor/default_dispatcher.ts b/lib/event_processor/default_dispatcher.ts new file mode 100644 index 000000000..2097cb82c --- /dev/null +++ b/lib/event_processor/default_dispatcher.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { RequestHandler } from '../utils/http_request_handler/http'; +import { EventDispatcher, EventDispatcherResponse, EventV1Request } from '../event_processor'; + +export class DefaultEventDispatcher implements EventDispatcher { + private requestHandler: RequestHandler; + + constructor(requestHandler: RequestHandler) { + this.requestHandler = requestHandler; + } + + async dispatchEvent( + eventObj: EventV1Request + ): Promise<EventDispatcherResponse> { + // Non-POST requests not supported + if (eventObj.httpVerb !== 'POST') { + return Promise.reject(new Error('Only POST requests are supported')); + } + + const dataString = JSON.stringify(eventObj.params); + + const headers = { + 'content-type': 'application/json', + 'content-length': dataString.length.toString(), + }; + + const abortableRequest = this.requestHandler.makeRequest(eventObj.url, headers, 'POST', dataString); + return abortableRequest.responsePromise; + } +} diff --git a/lib/modules/event_processor/eventDispatcher.ts b/lib/event_processor/eventDispatcher.ts similarity index 78% rename from lib/modules/event_processor/eventDispatcher.ts rename to lib/event_processor/eventDispatcher.ts index 15d261cf2..90b036862 100644 --- a/lib/modules/event_processor/eventDispatcher.ts +++ b/lib/event_processor/eventDispatcher.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,11 @@ import { EventV1 } from "./v1/buildEventV1"; export type EventDispatcherResponse = { - statusCode: number + statusCode?: number } -export type EventDispatcherCallback = (response: EventDispatcherResponse) => void - export interface EventDispatcher { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void + dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> } export interface EventV1Request { diff --git a/lib/modules/event_processor/eventProcessor.ts b/lib/event_processor/eventProcessor.ts similarity index 93% rename from lib/modules/event_processor/eventProcessor.ts rename to lib/event_processor/eventProcessor.ts index e0b31cc3a..fa2cab200 100644 --- a/lib/modules/event_processor/eventProcessor.ts +++ b/lib/event_processor/eventProcessor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023 Optimizely + * Copyright 2022-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ import { Managed } from './managed' import { ConversionEvent, ImpressionEvent } from './events' import { EventV1Request } from './eventDispatcher' import { EventQueue, DefaultEventQueue, SingleEventQueue, EventQueueSink } from './eventQueue' -import { getLogger } from '../logging' -import { NOTIFICATION_TYPES } from '../../utils/enums' -import { NotificationSender } from '../../core/notification_center' +import { getLogger } from '../modules/logging' +import { NOTIFICATION_TYPES } from '../utils/enums' +import { NotificationSender } from '../core/notification_center' export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s export const DEFAULT_BATCH_SIZE = 10 diff --git a/lib/modules/event_processor/eventQueue.ts b/lib/event_processor/eventQueue.ts similarity index 97% rename from lib/modules/event_processor/eventQueue.ts rename to lib/event_processor/eventQueue.ts index ac9d2ac66..3b8a71966 100644 --- a/lib/modules/event_processor/eventQueue.ts +++ b/lib/event_processor/eventQueue.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getLogger } from '../logging'; +import { getLogger } from '../modules/logging'; // TODO change this to use Managed from js-sdk-models when available import { Managed } from './managed'; diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts new file mode 100644 index 000000000..b63471a29 --- /dev/null +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +vi.mock('./default_dispatcher.browser', () => { + return { default: {} }; +}); + +vi.mock('./forwarding_event_processor', () => { + const getForwardingEventProcessor = vi.fn().mockReturnValue({}); + return { getForwardingEventProcessor }; +}); + +import { createForwardingEventProcessor } from './event_processor_factory.browser'; +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import browserDefaultEventDispatcher from './default_dispatcher.browser'; + +describe('createForwardingEventProcessor', () => { + const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); + + beforeEach(() => { + mockGetForwardingEventProcessor.mockClear(); + }); + + it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createForwardingEventProcessor(eventDispatcher); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); + }); + + it('uses the browser default event dispatcher if none is provided', () => { + const processor = createForwardingEventProcessor(); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, browserDefaultEventDispatcher); + }); +}); diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts new file mode 100644 index 000000000..ea4d2d2b1 --- /dev/null +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import { EventDispatcher } from './eventDispatcher'; +import { EventProcessor } from './eventProcessor'; +import defaultEventDispatcher from './default_dispatcher.browser'; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher = defaultEventDispatcher, +): EventProcessor => { + return getForwardingEventProcessor(eventDispatcher); +}; diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts new file mode 100644 index 000000000..36d4ea1fa --- /dev/null +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +vi.mock('./default_dispatcher.node', () => { + return { default: {} }; +}); + +vi.mock('./forwarding_event_processor', () => { + const getForwardingEventProcessor = vi.fn().mockReturnValue({}); + return { getForwardingEventProcessor }; +}); + +import { createForwardingEventProcessor } from './event_processor_factory.node'; +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import nodeDefaultEventDispatcher from './default_dispatcher.node'; + +describe('createForwardingEventProcessor', () => { + const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); + + beforeEach(() => { + mockGetForwardingEventProcessor.mockClear(); + }); + + it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createForwardingEventProcessor(eventDispatcher); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); + }); + + it('uses the node default event dispatcher if none is provided', () => { + const processor = createForwardingEventProcessor(); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, nodeDefaultEventDispatcher); + }); +}); diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts new file mode 100644 index 000000000..ae793ce4f --- /dev/null +++ b/lib/event_processor/event_processor_factory.node.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import { EventDispatcher } from './eventDispatcher'; +import { EventProcessor } from './eventProcessor'; +import defaultEventDispatcher from './default_dispatcher.node'; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher = defaultEventDispatcher, +): EventProcessor => { + return getForwardingEventProcessor(eventDispatcher); +}; diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts new file mode 100644 index 000000000..6de989534 --- /dev/null +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +vi.mock('./default_dispatcher.browser', () => { + return { default: {} }; +}); + +vi.mock('./forwarding_event_processor', () => { + const getForwardingEventProcessor = vi.fn().mockReturnValue({}); + return { getForwardingEventProcessor }; +}); + +import { createForwardingEventProcessor } from './event_processor_factory.react_native'; +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import browserDefaultEventDispatcher from './default_dispatcher.browser'; + +describe('createForwardingEventProcessor', () => { + const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); + + beforeEach(() => { + mockGetForwardingEventProcessor.mockClear(); + }); + + it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createForwardingEventProcessor(eventDispatcher); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); + }); + + it('uses the browser default event dispatcher if none is provided', () => { + const processor = createForwardingEventProcessor(); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, browserDefaultEventDispatcher); + }); +}); diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts new file mode 100644 index 000000000..3763a15c1 --- /dev/null +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import { EventDispatcher } from './eventDispatcher'; +import { EventProcessor } from './eventProcessor'; +import defaultEventDispatcher from './default_dispatcher.browser'; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher = defaultEventDispatcher, +): EventProcessor => { + return getForwardingEventProcessor(eventDispatcher); +}; diff --git a/lib/modules/event_processor/events.ts b/lib/event_processor/events.ts similarity index 98% rename from lib/modules/event_processor/events.ts rename to lib/event_processor/events.ts index 65cce503b..4254a274f 100644 --- a/lib/modules/event_processor/events.ts +++ b/lib/event_processor/events.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/event_processor/forwarding_event_processor.spec.ts b/lib/event_processor/forwarding_event_processor.spec.ts new file mode 100644 index 000000000..72da66633 --- /dev/null +++ b/lib/event_processor/forwarding_event_processor.spec.ts @@ -0,0 +1,98 @@ +/** + * Copyright 2021, 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it, vi } from 'vitest'; + +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import { EventDispatcher, makeBatchedEventV1 } from '.'; + +function createImpressionEvent() { + return { + type: 'impression' as const, + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: '1', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: 'layerId', + }, + + experiment: { + id: 'expId', + key: 'expKey', + }, + + variation: { + id: 'varId', + key: 'varKey', + }, + + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true, + } +} + +const getMockEventDispatcher = (): EventDispatcher => { + return { + dispatchEvent: vi.fn().mockResolvedValue({ statusCode: 200 }), + }; +}; + +const getMockNotificationCenter = () => { + return { + sendNotifications: vi.fn(), + }; +} + +describe('ForwardingEventProcessor', function() { + it('should dispatch event immediately when process is called', () => { + const dispatcher = getMockEventDispatcher(); + const mockDispatch = vi.mocked(dispatcher.dispatchEvent); + const notificationCenter = getMockNotificationCenter(); + const processor = getForwardingEventProcessor(dispatcher, notificationCenter); + processor.start(); + const event = createImpressionEvent(); + processor.process(event); + expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); + const data = mockDispatch.mock.calls[0][0].params; + expect(data).toEqual(makeBatchedEventV1([event])); + expect(notificationCenter.sendNotifications).toHaveBeenCalledOnce(); + }); + + it('should return a resolved promise when stop is called', async () => { + const dispatcher = getMockEventDispatcher(); + const notificationCenter = getMockNotificationCenter(); + const processor = getForwardingEventProcessor(dispatcher, notificationCenter); + processor.start(); + const stopPromise = processor.stop(); + expect(stopPromise).resolves.not.toThrow(); + }); + }); diff --git a/lib/plugins/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts similarity index 72% rename from lib/plugins/event_processor/forwarding_event_processor.ts rename to lib/event_processor/forwarding_event_processor.ts index e528e0202..919710c53 100644 --- a/lib/plugins/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021-2023, Optimizely + * Copyright 2021-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ import { EventProcessor, ProcessableEvent, -} from '../../modules/event_processor'; -import { NotificationSender } from '../../core/notification_center'; +} from '.'; +import { NotificationSender } from '../core/notification_center'; -import { EventDispatcher } from '../../shared_types'; -import { NOTIFICATION_TYPES } from '../../utils/enums'; -import { formatEvents } from '../../core/event_builder/build_event_v1'; +import { EventDispatcher } from '../shared_types'; +import { NOTIFICATION_TYPES } from '../utils/enums'; +import { formatEvents } from '../core/event_builder/build_event_v1'; class ForwardingEventProcessor implements EventProcessor { private dispatcher: EventDispatcher; @@ -35,7 +35,7 @@ class ForwardingEventProcessor implements EventProcessor { process(event: ProcessableEvent): void { const formattedEvent = formatEvents([event]); - this.dispatcher.dispatchEvent(formattedEvent, () => {}); + this.dispatcher.dispatchEvent(formattedEvent).catch(() => {}); if (this.NotificationSender) { this.NotificationSender.sendNotifications( NOTIFICATION_TYPES.LOG_EVENT, @@ -53,6 +53,6 @@ class ForwardingEventProcessor implements EventProcessor { } } -export function createForwardingEventProcessor(dispatcher: EventDispatcher, notificationSender?: NotificationSender): EventProcessor { +export function getForwardingEventProcessor(dispatcher: EventDispatcher, notificationSender?: NotificationSender): EventProcessor { return new ForwardingEventProcessor(dispatcher, notificationSender); } diff --git a/lib/modules/event_processor/index.react_native.ts b/lib/event_processor/index.react_native.ts similarity index 95% rename from lib/modules/event_processor/index.react_native.ts rename to lib/event_processor/index.react_native.ts index 91bb29a58..27a6f3a3a 100644 --- a/lib/modules/event_processor/index.react_native.ts +++ b/lib/event_processor/index.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/modules/event_processor/index.ts b/lib/event_processor/index.ts similarity index 95% rename from lib/modules/event_processor/index.ts rename to lib/event_processor/index.ts index c4eaef01d..c91ca2d21 100644 --- a/lib/modules/event_processor/index.ts +++ b/lib/event_processor/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/modules/event_processor/managed.ts b/lib/event_processor/managed.ts similarity index 94% rename from lib/modules/event_processor/managed.ts rename to lib/event_processor/managed.ts index 40b786380..dfb94e0f5 100644 --- a/lib/modules/event_processor/managed.ts +++ b/lib/event_processor/managed.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/modules/event_processor/pendingEventsDispatcher.ts b/lib/event_processor/pendingEventsDispatcher.ts similarity index 73% rename from lib/modules/event_processor/pendingEventsDispatcher.ts rename to lib/event_processor/pendingEventsDispatcher.ts index 4f4c8c61b..cfa2c3e80 100644 --- a/lib/modules/event_processor/pendingEventsDispatcher.ts +++ b/lib/event_processor/pendingEventsDispatcher.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../logging' -import { EventDispatcher, EventV1Request, EventDispatcherCallback } from './eventDispatcher' +import { getLogger } from '../modules/logging' +import { EventDispatcher, EventV1Request, EventDispatcherResponse } from './eventDispatcher' import { PendingEventsStore, LocalStorageStore } from './pendingEventsStore' -import { uuid, getTimestamp } from '../../utils/fns' +import { uuid, getTimestamp } from '../utils/fns' const logger = getLogger('EventProcessor') @@ -41,14 +41,13 @@ export class PendingEventsDispatcher implements EventDispatcher { this.store = store } - dispatchEvent(request: EventV1Request, callback: EventDispatcherCallback): void { - this.send( + dispatchEvent(request: EventV1Request): Promise<EventDispatcherResponse> { + return this.send( { uuid: uuid(), timestamp: getTimestamp(), request, - }, - callback, + } ) } @@ -58,22 +57,18 @@ export class PendingEventsDispatcher implements EventDispatcher { logger.debug('Sending %s pending events from previous page', pendingEvents.length) pendingEvents.forEach(item => { - try { - this.send(item, () => {}) - } catch (e) - { - logger.debug(String(e)) - } + this.send(item).catch((e) => { + logger.debug(String(e)); + }); }) } - protected send(entry: DispatcherEntry, callback: EventDispatcherCallback): void { + protected async send(entry: DispatcherEntry): Promise<EventDispatcherResponse> { this.store.set(entry.uuid, entry) - this.dispatcher.dispatchEvent(entry.request, response => { - this.store.remove(entry.uuid) - callback(response) - }) + const response = await this.dispatcher.dispatchEvent(entry.request); + this.store.remove(entry.uuid); + return response; } } diff --git a/lib/modules/event_processor/pendingEventsStore.ts b/lib/event_processor/pendingEventsStore.ts similarity index 95% rename from lib/modules/event_processor/pendingEventsStore.ts rename to lib/event_processor/pendingEventsStore.ts index 6b5ee3393..ca8dbf0f7 100644 --- a/lib/modules/event_processor/pendingEventsStore.ts +++ b/lib/event_processor/pendingEventsStore.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { objectValues } from '../../utils/fns' -import { getLogger } from '../logging'; +import { objectValues } from '../utils/fns' +import { getLogger } from '../modules/logging'; const logger = getLogger('EventProcessor') diff --git a/lib/modules/event_processor/reactNativeEventsStore.ts b/lib/event_processor/reactNativeEventsStore.ts similarity index 90% rename from lib/modules/event_processor/reactNativeEventsStore.ts rename to lib/event_processor/reactNativeEventsStore.ts index b0ef113f3..cf7dce9c8 100644 --- a/lib/modules/event_processor/reactNativeEventsStore.ts +++ b/lib/event_processor/reactNativeEventsStore.ts @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../logging' -import { objectValues } from "../../utils/fns" +import { getLogger } from '../modules/logging' +import { objectValues } from '../utils/fns' import { Synchronizer } from './synchronizer' -import ReactNativeAsyncStorageCache from '../../plugins/key_value_cache/reactNativeAsyncStorageCache'; -import PersistentKeyValueCache from '../../plugins/key_value_cache/persistentKeyValueCache'; +import ReactNativeAsyncStorageCache from '../plugins/key_value_cache/reactNativeAsyncStorageCache'; +import PersistentKeyValueCache from '../plugins/key_value_cache/persistentKeyValueCache'; const logger = getLogger('ReactNativeEventsStore') diff --git a/lib/modules/event_processor/requestTracker.ts b/lib/event_processor/requestTracker.ts similarity index 98% rename from lib/modules/event_processor/requestTracker.ts rename to lib/event_processor/requestTracker.ts index ab18b36d1..192919884 100644 --- a/lib/modules/event_processor/requestTracker.ts +++ b/lib/event_processor/requestTracker.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/modules/event_processor/synchronizer.ts b/lib/event_processor/synchronizer.ts similarity index 97% rename from lib/modules/event_processor/synchronizer.ts rename to lib/event_processor/synchronizer.ts index d6bf32b7b..f0659d7af 100644 --- a/lib/modules/event_processor/synchronizer.ts +++ b/lib/event_processor/synchronizer.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/modules/event_processor/v1/buildEventV1.ts b/lib/event_processor/v1/buildEventV1.ts similarity index 99% rename from lib/modules/event_processor/v1/buildEventV1.ts rename to lib/event_processor/v1/buildEventV1.ts index 699498dc4..1232d52ec 100644 --- a/lib/modules/event_processor/v1/buildEventV1.ts +++ b/lib/event_processor/v1/buildEventV1.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts b/lib/event_processor/v1/v1EventProcessor.react_native.ts similarity index 92% rename from lib/modules/event_processor/v1/v1EventProcessor.react_native.ts rename to lib/event_processor/v1/v1EventProcessor.react_native.ts index bd40a88bd..f4998a37b 100644 --- a/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts +++ b/lib/event_processor/v1/v1EventProcessor.react_native.ts @@ -16,13 +16,13 @@ import { uuid as id, objectEntries, -} from '../../../utils/fns' +} from '../../utils/fns' import { NetInfoState, addEventListener as addConnectionListener, } from "@react-native-community/netinfo" -import { getLogger } from '../../logging' -import { NotificationSender } from '../../../core/notification_center' +import { getLogger } from '../../modules/logging' +import { NotificationSender } from '../../core/notification_center' import { getQueue, @@ -43,9 +43,8 @@ import { formatEvents } from './buildEventV1' import { EventV1Request, EventDispatcher, - EventDispatcherResponse, } from '../eventDispatcher' -import { PersistentCacheProvider } from '../../../shared_types' +import { PersistentCacheProvider } from '../../shared_types' const logger = getLogger('ReactNativeEventProcessor') @@ -57,6 +56,7 @@ const EVENT_BUFFER_STORE_KEY = 'fs_optly_event_buffer' * React Native Events Processor with Caching support for events when app is offline. */ export class LogTierV1EventProcessor implements EventProcessor { + private id = Math.random(); private dispatcher: EventDispatcher // expose for testing public queue: EventQueue<ProcessableEvent> @@ -147,7 +147,6 @@ export class LogTierV1EventProcessor implements EventProcessor { // Retry pending failed events while draining queue await this.processPendingEvents() - logger.debug('draining queue with %s events', buffer.length) const eventCacheKey = id() @@ -199,16 +198,19 @@ export class LogTierV1EventProcessor implements EventProcessor { private async dispatchEvent(eventCacheKey: string, event: EventV1Request): Promise<void> { const requestPromise = new Promise<void>((resolve) => { - this.dispatcher.dispatchEvent(event, async ({ statusCode }: EventDispatcherResponse) => { - if (this.isSuccessResponse(statusCode)) { - await this.pendingEventsStore.remove(eventCacheKey) + this.dispatcher.dispatchEvent(event).then((response) => { + if (!response.statusCode || this.isSuccessResponse(response.statusCode)) { + return this.pendingEventsStore.remove(eventCacheKey) } else { this.shouldSkipDispatchToPreserveSequence = true - logger.warn('Failed to dispatch event, Response status Code: %s', statusCode) + logger.warn('Failed to dispatch event, Response status Code: %s', response.statusCode) + return Promise.resolve() } - resolve() - }) - sendEventNotification(this.notificationSender, event) + }).catch((e) => { + logger.warn('Failed to dispatch event, error: %s', e.message) + }).finally(() => resolve()) + + sendEventNotification(this.notificationSender, event) }) // Tracking all the requests to dispatch to make sure request is completed before fulfilling the `stop` promise this.requestTracker.trackRequest(requestPromise) diff --git a/lib/modules/event_processor/v1/v1EventProcessor.ts b/lib/event_processor/v1/v1EventProcessor.ts similarity index 91% rename from lib/modules/event_processor/v1/v1EventProcessor.ts rename to lib/event_processor/v1/v1EventProcessor.ts index 235fae83b..aac5103ef 100644 --- a/lib/modules/event_processor/v1/v1EventProcessor.ts +++ b/lib/event_processor/v1/v1EventProcessor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../../logging' -import { NotificationSender } from '../../../core/notification_center' +import { getLogger } from '../../modules/logging' +import { NotificationSender } from '../../core/notification_center' import { EventDispatcher } from '../eventDispatcher' import { @@ -83,7 +83,9 @@ export class LogTierV1EventProcessor implements EventProcessor { const dispatcher = useClosingDispatcher && this.closingDispatcher ? this.closingDispatcher : this.dispatcher; - dispatcher.dispatchEvent(formattedEvent, () => { + // TODO: this does not do anything if the dispatcher fails + // to dispatch. What should be done in that case? + dispatcher.dispatchEvent(formattedEvent).finally(() => { resolve() }) sendEventNotification(this.notificationCenter, formattedEvent) diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 8b43a4902..3d3952189 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -122,17 +122,18 @@ describe('javascript-sdk (Browser)', function() { delete global.XMLHttpRequest; }); - describe('when an eventDispatcher is not passed in', function() { - it('should wrap the default eventDispatcher and invoke sendPendingEvents', function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - logger: silentLogger, - }); - - sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - }); - }); + // TODO: pending event handling will be done by EventProcessor instead + // describe('when an eventDispatcher is not passed in', function() { + // it('should wrap the default eventDispatcher and invoke sendPendingEvents', function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager(), + // errorHandler: fakeErrorHandler, + // logger: silentLogger, + // }); + + // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); + // }); + // }); describe('when an eventDispatcher is passed in', function() { it('should NOT wrap the default eventDispatcher and invoke sendPendingEvents', function() { @@ -147,24 +148,26 @@ describe('javascript-sdk (Browser)', function() { }); }); - it('should invoke resendPendingEvents at most once', function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - logger: silentLogger, - }); + // TODO: pending event handling should be part of the event processor + // logic, not the dispatcher. Refactor accordingly. + // it('should invoke resendPendingEvents at most once', function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager(), + // errorHandler: fakeErrorHandler, + // logger: silentLogger, + // }); - sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); + // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - logger: silentLogger, - }); - optlyInstance.onReady().catch(function() {}); + // optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager(), + // errorHandler: fakeErrorHandler, + // logger: silentLogger, + // }); + // optlyInstance.onReady().catch(function() {}); - sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - }); + // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); + // }); it('should not throw if the provided config is not valid', function() { configValidator.validate.throws(new Error('Invalid config or something')); @@ -438,149 +441,151 @@ describe('javascript-sdk (Browser)', function() { }); }); - describe('event processor configuration', function() { - beforeEach(function() { - sinon.stub(eventProcessor, 'createEventProcessor'); - }); - - afterEach(function() { - eventProcessor.createEventProcessor.restore(); - }); - - it('should use default event flush interval when none is provided', function() { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - flushInterval: 1000, - }) - ); - }); - - describe('with an invalid flush interval', function() { - beforeEach(function() { - sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(false); - }); - - afterEach(function() { - eventProcessorConfigValidator.validateEventFlushInterval.restore(); - }); - - it('should ignore the event flush interval and use the default instead', function() { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventFlushInterval: ['invalid', 'flush', 'interval'], - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - flushInterval: 1000, - }) - ); - }); - }); - - describe('with a valid flush interval', function() { - beforeEach(function() { - sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(true); - }); - - afterEach(function() { - eventProcessorConfigValidator.validateEventFlushInterval.restore(); - }); - - it('should use the provided event flush interval', function() { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventFlushInterval: 9000, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - flushInterval: 9000, - }) - ); - }); - }); - - it('should use default event batch size when none is provided', function() { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - batchSize: 10, - }) - ); - }); - - describe('with an invalid event batch size', function() { - beforeEach(function() { - sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(false); - }); - - afterEach(function() { - eventProcessorConfigValidator.validateEventBatchSize.restore(); - }); - - it('should ignore the event batch size and use the default instead', function() { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventBatchSize: null, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - batchSize: 10, - }) - ); - }); - }); - - describe('with a valid event batch size', function() { - beforeEach(function() { - sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(true); - }); - - afterEach(function() { - eventProcessorConfigValidator.validateEventBatchSize.restore(); - }); - - it('should use the provided event batch size', function() { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventBatchSize: 300, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - batchSize: 300, - }) - ); - }); - }); - }); + // TODO: user will create and inject an event processor + // these tests will be refactored accordingly + // describe('event processor configuration', function() { + // beforeEach(function() { + // sinon.stub(eventProcessor, 'createEventProcessor'); + // }); + + // afterEach(function() { + // eventProcessor.createEventProcessor.restore(); + // }); + + // it('should use default event flush interval when none is provided', function() { + // optimizelyFactory.createInstance({ + // datafile: testData.getTestProjectConfigWithFeatures(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: silentLogger, + // }); + // sinon.assert.calledWithExactly( + // eventProcessor.createEventProcessor, + // sinon.match({ + // flushInterval: 1000, + // }) + // ); + // }); + + // describe('with an invalid flush interval', function() { + // beforeEach(function() { + // sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(false); + // }); + + // afterEach(function() { + // eventProcessorConfigValidator.validateEventFlushInterval.restore(); + // }); + + // it('should ignore the event flush interval and use the default instead', function() { + // optimizelyFactory.createInstance({ + // datafile: testData.getTestProjectConfigWithFeatures(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: silentLogger, + // eventFlushInterval: ['invalid', 'flush', 'interval'], + // }); + // sinon.assert.calledWithExactly( + // eventProcessor.createEventProcessor, + // sinon.match({ + // flushInterval: 1000, + // }) + // ); + // }); + // }); + + // describe('with a valid flush interval', function() { + // beforeEach(function() { + // sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(true); + // }); + + // afterEach(function() { + // eventProcessorConfigValidator.validateEventFlushInterval.restore(); + // }); + + // it('should use the provided event flush interval', function() { + // optimizelyFactory.createInstance({ + // datafile: testData.getTestProjectConfigWithFeatures(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: silentLogger, + // eventFlushInterval: 9000, + // }); + // sinon.assert.calledWithExactly( + // eventProcessor.createEventProcessor, + // sinon.match({ + // flushInterval: 9000, + // }) + // ); + // }); + // }); + + // it('should use default event batch size when none is provided', function() { + // optimizelyFactory.createInstance({ + // datafile: testData.getTestProjectConfigWithFeatures(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: silentLogger, + // }); + // sinon.assert.calledWithExactly( + // eventProcessor.createEventProcessor, + // sinon.match({ + // batchSize: 10, + // }) + // ); + // }); + + // describe('with an invalid event batch size', function() { + // beforeEach(function() { + // sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(false); + // }); + + // afterEach(function() { + // eventProcessorConfigValidator.validateEventBatchSize.restore(); + // }); + + // it('should ignore the event batch size and use the default instead', function() { + // optimizelyFactory.createInstance({ + // datafile: testData.getTestProjectConfigWithFeatures(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: silentLogger, + // eventBatchSize: null, + // }); + // sinon.assert.calledWithExactly( + // eventProcessor.createEventProcessor, + // sinon.match({ + // batchSize: 10, + // }) + // ); + // }); + // }); + + // describe('with a valid event batch size', function() { + // beforeEach(function() { + // sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(true); + // }); + + // afterEach(function() { + // eventProcessorConfigValidator.validateEventBatchSize.restore(); + // }); + + // it('should use the provided event batch size', function() { + // optimizelyFactory.createInstance({ + // datafile: testData.getTestProjectConfigWithFeatures(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: silentLogger, + // eventBatchSize: 300, + // }); + // sinon.assert.calledWithExactly( + // eventProcessor.createEventProcessor, + // sinon.match({ + // batchSize: 300, + // }) + // ); + // }); + // }); + // }); }); describe('ODP/ATS', () => { diff --git a/lib/index.browser.ts b/lib/index.browser.ts index f80a7b2c3..fd92d72c9 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -16,10 +16,10 @@ import logHelper from './modules/logging/logger'; import { getLogger, setErrorHandler, getErrorHandler, LogLevel } from './modules/logging'; -import { LocalStoragePendingEventsDispatcher } from './modules/event_processor'; +import { LocalStoragePendingEventsDispatcher } from './event_processor'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; -import defaultEventDispatcher from './plugins/event_dispatcher/index.browser'; +import defaultEventDispatcher from './event_processor/default_dispatcher.browser'; import sendBeaconEventDispatcher from './plugins/event_dispatcher/send_beacon_dispatcher'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; @@ -34,6 +34,7 @@ import { getUserAgentParser } from './plugins/odp/user_agent_parser/index.browse import * as commonExports from './common_exports'; import { PollingConfigManagerConfig } from './project_config/config_manager_factory'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; +import { createForwardingEventProcessor } from './event_processor/event_processor_factory.browser'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -77,55 +78,55 @@ const createInstance = function(config: Config): Client | null { logger.error(ex); } - let eventDispatcher; - // prettier-ignore - if (config.eventDispatcher == null) { // eslint-disable-line eqeqeq - // only wrap the event dispatcher with pending events retry if the user didnt override - eventDispatcher = new LocalStoragePendingEventsDispatcher({ - eventDispatcher: defaultEventDispatcher, - }); - - if (!hasRetriedEvents) { - eventDispatcher.sendPendingEvents(); - hasRetriedEvents = true; - } - } else { - eventDispatcher = config.eventDispatcher; - } - - let closingDispatcher = config.closingEventDispatcher; - - if (!config.eventDispatcher && !closingDispatcher && window.navigator && 'sendBeacon' in window.navigator) { - closingDispatcher = sendBeaconEventDispatcher; - } - - let eventBatchSize = config.eventBatchSize; - let eventFlushInterval = config.eventFlushInterval; - - if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - } - if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - logger.warn( - 'Invalid eventFlushInterval %s, defaulting to %s', - config.eventFlushInterval, - DEFAULT_EVENT_FLUSH_INTERVAL - ); - eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - } + // let eventDispatcher; + // // prettier-ignore + // if (config.eventDispatcher == null) { // eslint-disable-line eqeqeq + // // only wrap the event dispatcher with pending events retry if the user didnt override + // eventDispatcher = new LocalStoragePendingEventsDispatcher({ + // eventDispatcher: defaultEventDispatcher, + // }); + + // if (!hasRetriedEvents) { + // eventDispatcher.sendPendingEvents(); + // hasRetriedEvents = true; + // } + // } else { + // eventDispatcher = config.eventDispatcher; + // } + + // let closingDispatcher = config.closingEventDispatcher; + + // if (!config.eventDispatcher && !closingDispatcher && window.navigator && 'sendBeacon' in window.navigator) { + // closingDispatcher = sendBeaconEventDispatcher; + // } + + // let eventBatchSize = config.eventBatchSize; + // let eventFlushInterval = config.eventFlushInterval; + + // if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { + // logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); + // eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; + // } + // if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { + // logger.warn( + // 'Invalid eventFlushInterval %s, defaulting to %s', + // config.eventFlushInterval, + // DEFAULT_EVENT_FLUSH_INTERVAL + // ); + // eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; + // } const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - const eventProcessorConfig = { - dispatcher: eventDispatcher, - closingDispatcher, - flushInterval: eventFlushInterval, - batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - notificationCenter, - }; + // const eventProcessorConfig = { + // dispatcher: eventDispatcher, + // closingDispatcher, + // flushInterval: eventFlushInterval, + // batchSize: eventBatchSize, + // maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, + // notificationCenter, + // }; const odpExplicitlyOff = config.odpOptions?.disabled === true; if (odpExplicitlyOff) { @@ -137,7 +138,7 @@ const createInstance = function(config: Config): Client | null { const optimizelyOptions: OptimizelyOptions = { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, - eventProcessor: eventProcessor.createEventProcessor(eventProcessorConfig), + // eventProcessor: eventProcessor.createEventProcessor(eventProcessorConfig), logger, errorHandler, notificationCenter, @@ -197,6 +198,7 @@ export { IUserAgentParser, getUserAgentParser, createPollingProjectConfigManager, + createForwardingEventProcessor, }; export * from './common_exports'; @@ -215,6 +217,7 @@ export default { OptimizelyDecideOption, getUserAgentParser, createPollingProjectConfigManager, + createForwardingEventProcessor, }; export * from './export_types'; diff --git a/lib/index.lite.ts b/lib/index.lite.ts index b6a6bdfe9..b7fb41def 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -28,7 +28,6 @@ import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import Optimizely from './optimizely'; import { createNotificationCenter } from './core/notification_center'; -import { createForwardingEventProcessor } from './plugins/event_processor/forwarding_event_processor'; import { OptimizelyDecideOption, Client, ConfigLite } from './shared_types'; import * as commonExports from './common_exports'; @@ -69,15 +68,12 @@ setLogLevel(LogLevel.ERROR); const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - const eventDispatcher = config.eventDispatcher || noOpEventDispatcher; - const eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); const optimizelyOptions = { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, logger, errorHandler, - eventProcessor, notificationCenter, isValidInstance: isValidInstance, }; diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 4acbdf5f6..8ff0edeff 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -24,7 +24,6 @@ import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; -import { createProjectConfig } from './project_config/project_config'; describe('optimizelyFactory', function() { describe('APIs', function() { @@ -89,122 +88,124 @@ describe('optimizelyFactory', function() { assert.equal(optlyInstance.clientVersion, '5.3.4'); }); - describe('event processor configuration', function() { - var eventProcessorSpy; - beforeEach(function() { - eventProcessorSpy = sinon.stub(eventProcessor, 'createEventProcessor').callThrough(); - }); - - afterEach(function() { - eventProcessor.createEventProcessor.restore(); - }); - - it('should ignore invalid event flush interval and use default instead', function() { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventFlushInterval: ['invalid', 'flush', 'interval'], - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - flushInterval: 30000, - }) - ); - }); - - it('should use default event flush interval when none is provided', function() { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - flushInterval: 30000, - }) - ); - }); - - it('should use provided event flush interval when valid', function() { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventFlushInterval: 10000, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - flushInterval: 10000, - }) - ); - }); - - it('should ignore invalid event batch size and use default instead', function() { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventBatchSize: null, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - batchSize: 10, - }) - ); - }); - - it('should use default event batch size when none is provided', function() { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - batchSize: 10, - }) - ); - }); - - it('should use provided event batch size when valid', function() { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventBatchSize: 300, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - batchSize: 300, - }) - ); - }); - }); + // TODO: user will create and inject an event processor + // these tests will be refactored accordingly + // describe('event processor configuration', function() { + // var eventProcessorSpy; + // beforeEach(function() { + // eventProcessorSpy = sinon.stub(eventProcessor, 'createEventProcessor').callThrough(); + // }); + + // afterEach(function() { + // eventProcessor.createEventProcessor.restore(); + // }); + + // it('should ignore invalid event flush interval and use default instead', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventFlushInterval: ['invalid', 'flush', 'interval'], + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // flushInterval: 30000, + // }) + // ); + // }); + + // it('should use default event flush interval when none is provided', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // flushInterval: 30000, + // }) + // ); + // }); + + // it('should use provided event flush interval when valid', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventFlushInterval: 10000, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // flushInterval: 10000, + // }) + // ); + // }); + + // it('should ignore invalid event batch size and use default instead', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventBatchSize: null, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // batchSize: 10, + // }) + // ); + // }); + + // it('should use default event batch size when none is provided', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // batchSize: 10, + // }) + // ); + // }); + + // it('should use provided event batch size when valid', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventBatchSize: 300, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // batchSize: 300, + // }) + // ); + // }); + // }); }); }); }); diff --git a/lib/index.node.ts b/lib/index.node.ts index 0bb12d21e..98efc5d64 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -20,7 +20,7 @@ import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; -import defaultEventDispatcher from './plugins/event_dispatcher/index.node'; +import defaultEventDispatcher from './event_processor/default_dispatcher.node'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import { createNotificationCenter } from './core/notification_center'; import { createEventProcessor } from './plugins/event_processor'; @@ -28,6 +28,7 @@ import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { NodeOdpManager } from './plugins/odp_manager/index.node'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; +import { createForwardingEventProcessor } from './event_processor/event_processor_factory.node'; const logger = getLogger(); setLogLevel(LogLevel.ERROR); @@ -73,34 +74,35 @@ const createInstance = function(config: Config): Client | null { } } - let eventBatchSize = config.eventBatchSize; - let eventFlushInterval = config.eventFlushInterval; - - if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - } - if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - logger.warn( - 'Invalid eventFlushInterval %s, defaulting to %s', - config.eventFlushInterval, - DEFAULT_EVENT_FLUSH_INTERVAL - ); - eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - } + // let eventBatchSize = config.eventBatchSize; + // let eventFlushInterval = config.eventFlushInterval; + + // if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { + // logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); + // eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; + // } + // if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { + // logger.warn( + // 'Invalid eventFlushInterval %s, defaulting to %s', + // config.eventFlushInterval, + // DEFAULT_EVENT_FLUSH_INTERVAL + // ); + // eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; + // } const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - const eventProcessorConfig = { - dispatcher: config.eventDispatcher || defaultEventDispatcher, - flushInterval: eventFlushInterval, - batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - notificationCenter, - }; + // const eventProcessorConfig = { + // dispatcher: config.eventDispatcher || defaultEventDispatcher, + // flushInterval: eventFlushInterval, + // batchSize: eventBatchSize, + // maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, + // notificationCenter, + // }; - const eventProcessor = createEventProcessor(eventProcessorConfig); + // const eventProcessor = createEventProcessor(eventProcessorConfig); + // const eventProcessor = config.eventProcessor; const odpExplicitlyOff = config.odpOptions?.disabled === true; if (odpExplicitlyOff) { @@ -112,7 +114,7 @@ const createInstance = function(config: Config): Client | null { const optimizelyOptions = { clientEngine: enums.NODE_CLIENT_ENGINE, ...config, - eventProcessor, + // eventProcessor, logger, errorHandler, notificationCenter, @@ -141,7 +143,8 @@ export { setLogLevel, createInstance, OptimizelyDecideOption, - createPollingProjectConfigManager + createPollingProjectConfigManager, + createForwardingEventProcessor, }; export * from './common_exports'; @@ -156,7 +159,8 @@ export default { setLogLevel, createInstance, OptimizelyDecideOption, - createPollingProjectConfigManager + createPollingProjectConfigManager, + createForwardingEventProcessor, }; export * from './export_types'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 3be9b300c..b2654823d 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -20,7 +20,7 @@ import Optimizely from './optimizely'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import * as loggerPlugin from './plugins/logger/index.react_native'; -import defaultEventDispatcher from './plugins/event_dispatcher/index.browser'; +import defaultEventDispatcher from './event_processor/default_dispatcher.browser'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import { createNotificationCenter } from './core/notification_center'; import { createEventProcessor } from './plugins/event_processor/index.react_native'; @@ -28,6 +28,7 @@ import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; +import { createForwardingEventProcessor } from './event_processor/event_processor_factory.react_native'; import 'fast-text-encoding'; import 'react-native-get-random-values'; @@ -71,35 +72,35 @@ const createInstance = function(config: Config): Client | null { logger.error(ex); } - let eventBatchSize = config.eventBatchSize; - let eventFlushInterval = config.eventFlushInterval; - - if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - } - if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - logger.warn( - 'Invalid eventFlushInterval %s, defaulting to %s', - config.eventFlushInterval, - DEFAULT_EVENT_FLUSH_INTERVAL - ); - eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - } + // let eventBatchSize = config.eventBatchSize; + // let eventFlushInterval = config.eventFlushInterval; + + // if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { + // logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); + // eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; + // } + // if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { + // logger.warn( + // 'Invalid eventFlushInterval %s, defaulting to %s', + // config.eventFlushInterval, + // DEFAULT_EVENT_FLUSH_INTERVAL + // ); + // eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; + // } const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - const eventProcessorConfig = { - dispatcher: config.eventDispatcher || defaultEventDispatcher, - flushInterval: eventFlushInterval, - batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - notificationCenter, - peristentCacheProvider: config.persistentCacheProvider, - }; + // const eventProcessorConfig = { + // dispatcher: config.eventDispatcher || defaultEventDispatcher, + // flushInterval: eventFlushInterval, + // batchSize: eventBatchSize, + // maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, + // notificationCenter, + // peristentCacheProvider: config.persistentCacheProvider, + // }; - const eventProcessor = createEventProcessor(eventProcessorConfig); + // const eventProcessor = createEventProcessor(eventProcessorConfig); const odpExplicitlyOff = config.odpOptions?.disabled === true; if (odpExplicitlyOff) { @@ -111,7 +112,7 @@ const createInstance = function(config: Config): Client | null { const optimizelyOptions = { clientEngine: enums.REACT_NATIVE_JS_CLIENT_ENGINE, ...config, - eventProcessor, + // eventProcessor, logger, errorHandler, notificationCenter, @@ -146,6 +147,7 @@ export { createInstance, OptimizelyDecideOption, createPollingProjectConfigManager, + createForwardingEventProcessor, }; export * from './common_exports'; @@ -161,6 +163,7 @@ export default { createInstance, OptimizelyDecideOption, createPollingProjectConfigManager, + createForwardingEventProcessor, }; export * from './export_types'; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 9047ee71a..ca375151b 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -26,7 +26,6 @@ import AudienceEvaluator from '../core/audience_evaluator'; import * as bucketer from '../core/bucketer'; import * as projectConfigManager from '../project_config/project_config_manager'; import * as enums from '../utils/enums'; -import eventDispatcher from '../plugins/event_dispatcher/index.node'; import errorHandler from '../plugins/error_handler'; import fns from '../utils/fns'; import * as logger from '../plugins/logger'; @@ -34,10 +33,9 @@ import * as decisionService from '../core/decision_service'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; import * as projectConfig from '../project_config/project_config'; import testData from '../tests/test_data'; -import { createForwardingEventProcessor } from '../plugins/event_processor/forwarding_event_processor'; +import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; import { createEventProcessor } from '../plugins/event_processor'; import { createNotificationCenter } from '../core/notification_center'; -import { NodeOdpManager } from '../plugins/odp_manager/index.node'; import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; @@ -51,6 +49,17 @@ var FEATURE_VARIABLE_TYPES = enums.FEATURE_VARIABLE_TYPES; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); +const getMockEventDispatcher = () => { + const dispatcher = { + dispatchEvent: sinon.spy(() => Promise.resolve({ statusCode: 200 })), + } + return dispatcher; +} + +const getMockEventProcessor = (notificationCenter) => { + return getForwardingEventProcessor(getMockEventDispatcher(), notificationCenter); +} + describe('lib/optimizely', function() { var ProjectConfigManagerStub; var globalStubErrorHandler; @@ -77,7 +86,7 @@ describe('lib/optimizely', function() { onReady: sinon.stub().returns({ then: function() {} }), }; }); - sinon.stub(eventDispatcher, 'dispatchEvent'); + // sinon.stub(eventDispatcher, 'dispatchEvent'); clock = sinon.useFakeTimers(new Date()); }); @@ -85,7 +94,7 @@ describe('lib/optimizely', function() { ProjectConfigManagerStub.restore(); logging.resetErrorHandler(); logging.resetLogger(); - eventDispatcher.dispatchEvent.restore(); + // eventDispatcher.dispatchEvent.restore(); clock.restore(); }); @@ -98,7 +107,7 @@ describe('lib/optimizely', function() { }; var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: stubErrorHandler }); - var eventProcessor = createForwardingEventProcessor(stubEventDispatcher); + var eventProcessor = getForwardingEventProcessor(stubEventDispatcher); beforeEach(function() { sinon.stub(stubErrorHandler, 'handleError'); sinon.stub(createdLogger, 'log'); @@ -226,8 +235,9 @@ describe('lib/optimizely', function() { var optlyInstance; var bucketStub; var fakeDecisionResponse; + var eventDispatcher = getMockEventDispatcher(); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); - var eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); + var eventProcessor = getForwardingEventProcessor(eventDispatcher, notificationCenter); var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, @@ -239,10 +249,8 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - // datafile: testData.getTestProjectConfig(), projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -258,6 +266,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); errorHandler.handleError.restore(); createdLogger.log.restore(); @@ -2693,7 +2702,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -2756,6 +2765,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, + eventProcessor, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -4463,6 +4473,7 @@ describe('lib/optimizely', function() { logToConsole: false, }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = getMockEventDispatcher(); var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -4495,6 +4506,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); errorHandler.handleError.restore(); createdLogger.log.restore(); @@ -4605,6 +4617,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); errorHandler.handleError.restore(); createdLogger.log.restore(); fns.uuid.restore(); @@ -4968,7 +4981,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -4985,6 +4998,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); errorHandler.handleError.restore(); createdLogger.log.restore(); @@ -5082,7 +5096,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -5151,7 +5165,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -5165,6 +5179,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); @@ -5767,6 +5782,7 @@ describe('lib/optimizely', function() { describe('#decideForKeys', function() { var userId = 'tester'; beforeEach(function() { + eventDispatcher.dispatchEvent.reset(); const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), }); @@ -5775,7 +5791,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -5789,6 +5805,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); @@ -5886,7 +5903,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -5900,6 +5917,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); @@ -5992,13 +6010,12 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, eventBatchSize: 1, defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY], - eventProcessor, notificationCenter, }); @@ -6006,6 +6023,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); @@ -6084,6 +6102,7 @@ describe('lib/optimizely', function() { logToConsole: false, }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = getMockEventDispatcher(); var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -6098,7 +6117,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -6107,6 +6126,10 @@ describe('lib/optimizely', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + var userAttributes = { browser_type: 'firefox', }; @@ -6150,6 +6173,10 @@ describe('lib/optimizely', function() { var optlyInstance; var fakeDecisionResponse; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; + var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -6165,7 +6192,6 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -6174,6 +6200,7 @@ describe('lib/optimizely', function() { eventProcessor, }); + sandbox.stub(eventDispatcher, 'dispatchEvent'); sandbox.stub(errorHandler, 'handleError'); sandbox.stub(createdLogger, 'log'); sandbox.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); @@ -6196,7 +6223,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, eventProcessor, @@ -6228,6 +6255,10 @@ describe('lib/optimizely', function() { }; sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns(fakeDecisionResponse); }); + + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }); it('returns true and dispatches an impression event', function() { var user = optlyInstance.createUserContext('user1', attributes); @@ -6303,7 +6334,6 @@ describe('lib/optimizely', function() { }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - assert.isFunction(callArgs[1]); assert.equal( buildLogMessageFromArgs(createdLogger.log.lastCall.args), 'OPTIMIZELY: Feature test_feature_for_experiment is enabled for user user1.' @@ -6451,6 +6481,10 @@ describe('lib/optimizely', function() { sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns(fakeDecisionResponse); result = optlyInstance.isFeatureEnabled('shared_feature', 'user1', attributes); }); + + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }); it('should return false', function() { assert.strictEqual(result, false); @@ -6527,7 +6561,6 @@ describe('lib/optimizely', function() { }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - assert.isFunction(callArgs[1]); }); }); @@ -6543,6 +6576,10 @@ describe('lib/optimizely', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + it('should return false', function() { var result = optlyInstance.isFeatureEnabled('shared_feature', 'user1', attributes); var user = optlyInstance.createUserContext('user1', attributes); @@ -6732,12 +6769,16 @@ describe('lib/optimizely', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + it('returns an empty array if the instance is invalid', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, eventProcessor, @@ -6781,7 +6822,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -8979,6 +9020,9 @@ describe('lib/optimizely', function() { }); var optlyInstance; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -8993,7 +9037,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9002,6 +9046,7 @@ describe('lib/optimizely', function() { notificationCenter, }); + sandbox.stub(eventDispatcher, 'dispatchEvent'); sandbox.stub(errorHandler, 'handleError'); sandbox.stub(createdLogger, 'log'); }); @@ -9123,6 +9168,9 @@ describe('lib/optimizely', function() { var optlyInstance; var audienceEvaluator; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -9137,7 +9185,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9147,6 +9195,7 @@ describe('lib/optimizely', function() { }); audienceEvaluator = AudienceEvaluator.prototype; + sandbox.stub(eventDispatcher, 'dispatchEvent'); sandbox.stub(errorHandler, 'handleError'); sandbox.stub(createdLogger, 'log'); evalSpy = sandbox.spy(audienceEvaluator, 'evaluate'); @@ -9313,6 +9362,7 @@ describe('lib/optimizely', function() { var bucketStub; var fakeDecisionResponse; var notificationCenter; + var eventDispatcher; var eventProcessor; var createdLogger = logger.createLogger({ @@ -9326,6 +9376,7 @@ describe('lib/optimizely', function() { sinon.stub(createdLogger, 'log'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + eventDispatcher = getMockEventDispatcher(); eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 3, @@ -9335,6 +9386,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); errorHandler.handleError.restore(); createdLogger.log.restore(); @@ -9353,7 +9405,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9653,7 +9705,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9689,7 +9741,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9701,6 +9753,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); return eventProcessorStopPromise.catch(function() { // Handle rejected promise - don't want test to fail }); @@ -9725,6 +9778,7 @@ describe('lib/optimizely', function() { }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = getMockEventDispatcher(); var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -9737,6 +9791,7 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); errorHandler.handleError.restore(); createdLogger.log.restore(); }); @@ -9751,7 +9806,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', errorHandler: errorHandler, projectConfigManager, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9769,7 +9824,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager: getMockProjectConfigManager(), errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9779,6 +9834,7 @@ describe('lib/optimizely', function() { }); }); + it('returns fallback values from API methods that return meaningful values', function() { assert.isNull(optlyInstance.activate('my_experiment', 'user1')); assert.isNull(optlyInstance.getVariation('my_experiment', 'user1')); @@ -9822,7 +9878,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', projectConfigManager, errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9841,7 +9897,6 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', errorHandler: errorHandler, projectConfigManager, - eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9865,7 +9920,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', errorHandler: errorHandler, projectConfigManager, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9889,7 +9944,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', errorHandler: errorHandler, projectConfigManager, - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9911,7 +9966,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', errorHandler: errorHandler, projectConfigManager: getMockProjectConfigManager(), - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9941,7 +9996,7 @@ describe('lib/optimizely', function() { clientEngine: 'node-sdk', errorHandler: errorHandler, projectConfigManager: getMockProjectConfigManager(), - eventDispatcher: eventDispatcher, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9966,7 +10021,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, projectConfigManager: fakeProjectConfigManager, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -10051,7 +10106,7 @@ describe('lib/optimizely', function() { var eventProcessor; beforeEach(function() { bucketStub = sinon.stub(bucketer, 'bucket'); - eventDispatcherSpy = sinon.spy(); + eventDispatcherSpy = sinon.spy(() => Promise.resolve({ statusCode: 200 })); eventProcessor = createEventProcessor({ dispatcher: { dispatchEvent: eventDispatcherSpy }, batchSize: 1, @@ -10113,46 +10168,48 @@ describe('lib/optimizely', function() { // Note: /lib/index.browser.tests.js contains relevant Opti Client x Browser ODP Tests // TODO: Finish these tests in ODP Node.js Implementation describe('odp', () => { - var optlyInstanceWithOdp; - var bucketStub; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); - var eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); - var createdLogger = logger.createLogger({ - logLevel: LOG_LEVEL.INFO, - logToConsole: false, - }); - - beforeEach(function() { - const datafile = testData.getTestProjectConfig(); - const mockConfigManager = getMockProjectConfigManager(); - mockConfigManager.setConfig(createProjectConfig(datafile, JSON.stringify(datafile))); - - optlyInstanceWithOdp = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: mockConfigManager, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - eventProcessor, - notificationCenter, - odpManager: new NodeOdpManager({}), - }); - - bucketStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); - sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - }); - - afterEach(function() { - bucketer.bucket.restore(); - errorHandler.handleError.restore(); - createdLogger.log.restore(); - fns.uuid.restore(); - }); + // var optlyInstanceWithOdp; + // var bucketStub; + // var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); + // var eventDispatcher = getMockEventDispatcher(); + // var eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); + // var createdLogger = logger.createLogger({ + // logLevel: LOG_LEVEL.INFO, + // logToConsole: false, + // }); + + // beforeEach(function() { + // const datafile = testData.getTestProjectConfig(); + // const mockConfigManager = getMockProjectConfigManager(); + // mockConfigManager.setConfig(createProjectConfig(datafile, JSON.stringify(datafile))); + + // optlyInstanceWithOdp = new Optimizely({ + // clientEngine: 'node-sdk', + // projectConfigManager: mockConfigManager, + // errorHandler: errorHandler, + // eventDispatcher: eventDispatcher, + // jsonSchemaValidator: jsonSchemaValidator, + // logger: createdLogger, + // isValidInstance: true, + // eventBatchSize: 1, + // eventProcessor, + // notificationCenter, + // odpManager: new NodeOdpManager({}), + // }); + + // bucketStub = sinon.stub(bucketer, 'bucket'); + // sinon.stub(errorHandler, 'handleError'); + // sinon.stub(createdLogger, 'log'); + // sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); + // }); + + // afterEach(function() { + // eventDispatcher.dispatchEvent.reset(); + // bucketer.bucket.restore(); + // errorHandler.handleError.restore(); + // createdLogger.log.restore(); + // fns.uuid.restore(); + // }); it('should send an identify event when called with odp enabled', () => { // TODO diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 95d3682a3..c78154311 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -17,7 +17,7 @@ import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; -import { EventProcessor } from '../modules/event_processor'; +import { EventProcessor } from '../event_processor'; import { IOdpManager } from '../core/odp/odp_manager'; import { OdpConfig } from '../core/odp/odp_config'; @@ -95,7 +95,7 @@ export default class Optimizely implements Client { protected logger: LoggerFacade; private projectConfigManager: ProjectConfigManager; private decisionService: DecisionService; - private eventProcessor: EventProcessor; + private eventProcessor?: EventProcessor; private defaultDecideOptions: { [key: string]: boolean }; protected odpManager?: IOdpManager; public notificationCenter: NotificationCenter; @@ -171,7 +171,8 @@ export default class Optimizely implements Client { this.eventProcessor = config.eventProcessor; - const eventProcessorStartedPromise = this.eventProcessor.start(); + const eventProcessorStartedPromise = this.eventProcessor ? this.eventProcessor.start() : + Promise.resolve(undefined); this.readyPromise = Promise.all([ projectConfigManagerRunningPromise, @@ -276,6 +277,11 @@ export default class Optimizely implements Client { enabled: boolean, attributes?: UserAttributes ): void { + if (!this.eventProcessor) { + this.logger.error(ERROR_MESSAGES.NO_EVENT_PROCESSOR); + return; + } + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return; @@ -364,6 +370,11 @@ export default class Optimizely implements Client { */ track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void { try { + if (!this.eventProcessor) { + this.logger.error(ERROR_MESSAGES.NO_EVENT_PROCESSOR); + return; + } + if (!this.isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'track'); return; @@ -1304,7 +1315,9 @@ export default class Optimizely implements Client { this.notificationCenter.clearAllNotificationListeners(); - const eventProcessorStoppedPromise = this.eventProcessor.stop(); + const eventProcessorStoppedPromise = this.eventProcessor ? this.eventProcessor.stop() : + Promise.resolve(); + if (this.disposeOnUpdate) { this.disposeOnUpdate(); this.disposeOnUpdate = undefined; diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 8c436391c..54d34a953 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -27,13 +27,19 @@ import { createEventProcessor } from '../plugins/event_processor'; import { createNotificationCenter } from '../core/notification_center'; import Optimizely from '../optimizely'; import errorHandler from '../plugins/error_handler'; -import eventDispatcher from '../plugins/event_dispatcher/index.node'; import { CONTROL_ATTRIBUTES, LOG_LEVEL, LOG_MESSAGES } from '../utils/enums'; import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; +const getMockEventDispatcher = () => { + const dispatcher = { + dispatchEvent: sinon.spy(() => Promise.resolve({ statusCode: 200 })), + } + return dispatcher; +} + describe('lib/optimizely_user_context', function() { describe('APIs', function() { var fakeOptimizely; @@ -351,6 +357,7 @@ describe('lib/optimizely_user_context', function() { describe('when valid forced decision is set', function() { var optlyInstance; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = getMockEventDispatcher(); var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -370,13 +377,12 @@ describe('lib/optimizely_user_context', function() { }); sinon.stub(optlyInstance.decisionService.logger, 'log'); - sinon.stub(eventDispatcher, 'dispatchEvent'); sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); }); afterEach(function() { optlyInstance.decisionService.logger.log.restore(); - eventDispatcher.dispatchEvent.restore(); + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); @@ -686,6 +692,7 @@ describe('lib/optimizely_user_context', function() { describe('when invalid forced decision is set', function() { var optlyInstance; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = getMockEventDispatcher(); var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -705,6 +712,10 @@ describe('lib/optimizely_user_context', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + it('should NOT return forced decision object when forced decision is set for a flag', function() { var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; @@ -790,6 +801,7 @@ describe('lib/optimizely_user_context', function() { logToConsole: false, }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = getMockEventDispatcher(); var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, @@ -809,6 +821,10 @@ describe('lib/optimizely_user_context', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }); + it('should prioritize flag forced decision over experiment rule', function() { var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; @@ -835,6 +851,7 @@ describe('lib/optimizely_user_context', function() { logToConsole: false, }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var eventDispatcher = getMockEventDispatcher(); var eventProcessor = createEventProcessor({ dispatcher: eventDispatcher, batchSize: 1, diff --git a/lib/plugins/event_dispatcher/index.browser.tests.js b/lib/plugins/event_dispatcher/index.browser.tests.js deleted file mode 100644 index b5c548c5b..000000000 --- a/lib/plugins/event_dispatcher/index.browser.tests.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2016-2017, 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; -import sinon from 'sinon'; - -import { dispatchEvent } from './index.browser'; - -describe('lib/plugins/event_dispatcher/browser', function() { - describe('APIs', function() { - describe('dispatchEvent', function() { - beforeEach(function() { - this.requests = []; - global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); - global.XMLHttpRequest.onCreate = (req) => { this.requests.push(req); }; - }); - - afterEach(function() { - delete global.XMLHttpRequest - }); - - it('should send a POST request with the specified params', function(done) { - var eventParams = { testParam: 'testParamValue' }; - var eventObj = { - url: 'https://cdn.com/event', - body: { - id: 123, - }, - httpVerb: 'POST', - params: eventParams, - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - assert.strictEqual(1, this.requests.length); - assert.strictEqual(this.requests[0].method, 'POST'); - assert.strictEqual(this.requests[0].requestBody, JSON.stringify(eventParams)); - done(); - }); - - it('should execute the callback passed to event dispatcher with a post', function(done) { - var eventParams = { testParam: 'testParamValue' }; - var eventObj = { - url: 'https://cdn.com/event', - body: { - id: 123, - }, - httpVerb: 'POST', - params: eventParams, - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - this.requests[0].respond([ - 200, - {}, - '{"url":"https://cdn.com/event","body":{"id":123},"httpVerb":"POST","params":{"testParam":"testParamValue"}}', - ]); - sinon.assert.calledOnce(callback); - done(); - }); - - it('should execute the callback passed to event dispatcher with a get', function(done) { - var eventObj = { - url: 'https://cdn.com/event', - httpVerb: 'GET', - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - this.requests[0].respond([200, {}, '{"url":"https://cdn.com/event","httpVerb":"GET"']); - sinon.assert.calledOnce(callback); - done(); - }); - }); - }); -}); diff --git a/lib/plugins/event_dispatcher/index.browser.ts b/lib/plugins/event_dispatcher/index.browser.ts deleted file mode 100644 index 9f0da6d0a..000000000 --- a/lib/plugins/event_dispatcher/index.browser.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2016-2017, 2020-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -const POST_METHOD = 'POST'; -const GET_METHOD = 'GET'; -const READYSTATE_COMPLETE = 4; - -export interface Event { - url: string; - httpVerb: 'POST' | 'GET'; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: any; -} - - -/** - * Sample event dispatcher implementation for tracking impression and conversions - * Users of the SDK can provide their own implementation - * @param {Event} eventObj - * @param {Function} callback - */ -export const dispatchEvent = function( - eventObj: Event, - callback: (response: { statusCode: number; }) => void -): void { - const params = eventObj.params; - let url: string = eventObj.url; - let req: XMLHttpRequest; - if (eventObj.httpVerb === POST_METHOD) { - req = new XMLHttpRequest(); - req.open(POST_METHOD, url, true); - req.setRequestHeader('Content-Type', 'application/json'); - req.onreadystatechange = function() { - if (req.readyState === READYSTATE_COMPLETE && callback && typeof callback === 'function') { - try { - callback({ statusCode: req.status }); - } catch (e) { - // TODO: Log this somehow (consider adding a logger to the EventDispatcher interface) - } - } - }; - req.send(JSON.stringify(params)); - } else { - // add param for cors headers to be sent by the log endpoint - url += '?wxhr=true'; - if (params) { - url += '&' + toQueryString(params); - } - - req = new XMLHttpRequest(); - req.open(GET_METHOD, url, true); - req.onreadystatechange = function() { - if (req.readyState === READYSTATE_COMPLETE && callback && typeof callback === 'function') { - try { - callback({ statusCode: req.status }); - } catch (e) { - // TODO: Log this somehow (consider adding a logger to the EventDispatcher interface) - } - } - }; - req.send(); - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const toQueryString = function(obj: any): string { - return Object.keys(obj) - .map(function(k) { - return encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]); - }) - .join('&'); -}; - -export default { - dispatchEvent, -}; diff --git a/lib/plugins/event_dispatcher/index.node.tests.js b/lib/plugins/event_dispatcher/index.node.tests.js deleted file mode 100644 index 2afeccc7b..000000000 --- a/lib/plugins/event_dispatcher/index.node.tests.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright 2016-2018, 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import nock from 'nock'; -import sinon from 'sinon'; -import { assert } from 'chai'; - -import { dispatchEvent } from './index.node'; - -describe('lib/plugins/event_dispatcher/node', function() { - describe('APIs', function() { - describe('dispatchEvent', function() { - var stubCallback = { - callback: function() {}, - }; - - beforeEach(function() { - sinon.stub(stubCallback, 'callback'); - nock('https://cdn.com/') - .post('/event') - .reply(200, { - ok: true, - }); - }); - - afterEach(function() { - stubCallback.callback.restore(); - nock.cleanAll(); - }); - - it('should send a POST request with the specified params', function(done) { - var eventObj = { - url: 'https://cdn.com/event', - params: { - id: 123, - }, - httpVerb: 'POST', - }; - - dispatchEvent(eventObj, function(resp) { - assert.equal(200, resp.statusCode); - done(); - }); - }); - - it('should execute the callback passed to event dispatcher', function(done) { - var eventObj = { - url: 'https://cdn.com/event', - params: { - id: 123, - }, - httpVerb: 'POST', - }; - - dispatchEvent(eventObj, stubCallback.callback) - .on('response', function(response) { - sinon.assert.calledOnce(stubCallback.callback); - done(); - }) - .on('error', function(error) { - assert.fail('status code okay', 'status code not okay', ''); - }); - }); - - it('rejects GET httpVerb', function() { - var eventObj = { - url: 'https://cdn.com/event', - params: { - id: 123, - }, - httpVerb: 'GET', - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - sinon.assert.notCalled(callback); - }); - - describe('in the event of an error', function() { - beforeEach(function() { - nock('https://example/') - .post('/event') - .replyWithError('Connection error') - }); - - it('does not throw', function() { - var eventObj = { - url: 'https://example/event', - params: {}, - httpVerb: 'POST', - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - sinon.assert.notCalled(callback); - }); - }); - }); - }); -}); diff --git a/lib/plugins/event_dispatcher/index.node.ts b/lib/plugins/event_dispatcher/index.node.ts deleted file mode 100644 index 8efd7fb4f..000000000 --- a/lib/plugins/event_dispatcher/index.node.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright 2016-2018, 2020-2021, 2024 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import http from 'http'; -import https from 'https'; -import url from 'url'; - -import { Event } from '../../shared_types'; - -/** - * Dispatch an HTTP request to the given url and the specified options - * @param {Event} eventObj Event object containing - * @param {string} eventObj.url the url to make the request to - * @param {Object} eventObj.params parameters to pass to the request (i.e. in the POST body) - * @param {string} eventObj.httpVerb the HTTP request method type. only POST is supported. - * @param {function} callback callback to execute - * @return {ClientRequest|undefined} ClientRequest object which made the request, or undefined if no request was made (error) - */ -export const dispatchEvent = function( - eventObj: Event, - callback: (response: { statusCode: number }) => void -): http.ClientRequest | void { - // Non-POST requests not supported - if (eventObj.httpVerb !== 'POST') { - return; - } - - const parsedUrl = url.parse(eventObj.url); - - const dataString = JSON.stringify(eventObj.params); - - const requestOptions = { - host: parsedUrl.host, - path: parsedUrl.path, - method: 'POST', - headers: { - 'content-type': 'application/json', - 'content-length': dataString.length.toString(), - }, - }; - - const reqWrapper: { req?: http.ClientRequest } = {}; - - const requestCallback = function(response?: { statusCode: number }): void { - if (response && response.statusCode && response.statusCode >= 200 && response.statusCode < 400) { - callback(response); - } - reqWrapper.req?.destroy(); - reqWrapper.req = undefined; - }; - - reqWrapper.req = (parsedUrl.protocol === 'http:' ? http : https) - .request(requestOptions, requestCallback as (res: http.IncomingMessage) => void); - // Add no-op error listener to prevent this from throwing - reqWrapper.req.on('error', function() { - reqWrapper.req?.destroy(); - reqWrapper.req = undefined; - }); - reqWrapper.req.write(dataString); - reqWrapper.req.end(); - return reqWrapper.req; -}; - -export default { - dispatchEvent, -}; diff --git a/lib/plugins/event_dispatcher/no_op.ts b/lib/plugins/event_dispatcher/no_op.ts index 32353bae9..cbe2473d7 100644 --- a/lib/plugins/event_dispatcher/no_op.ts +++ b/lib/plugins/event_dispatcher/no_op.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021, Optimizely + * Copyright 2021, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,7 @@ import { Event } from '../../shared_types'; /* eslint-disable @typescript-eslint/no-unused-vars */ export const dispatchEvent = function( eventObj: Event, - callback: (response: { statusCode: number; }) => void -): void { +): any { // NoOp Event dispatcher. It does nothing really. } diff --git a/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts b/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts index 4cac7b7c3..3dabf0401 100644 --- a/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts +++ b/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { EventDispatcher } from '../../modules/event_processor/eventDispatcher'; +import { EventDispatcher, EventDispatcherResponse } from '../../event_processor'; export type Event = { url: string; @@ -31,17 +31,17 @@ export type Event = { */ export const dispatchEvent = function( eventObj: Event, - callback: (response: { statusCode: number; }) => void -): void { +): Promise<EventDispatcherResponse> { const { params, url } = eventObj; const blob = new Blob([JSON.stringify(params)], { type: "application/json", }); const success = navigator.sendBeacon(url, blob); - callback({ - statusCode: success ? 200 : 500, - }); + if(success) { + return Promise.resolve({}); + } + return Promise.reject(new Error('sendBeacon failed')); } const eventDispatcher : EventDispatcher = { diff --git a/lib/plugins/event_processor/forwarding_event_processor.tests.js b/lib/plugins/event_processor/forwarding_event_processor.tests.js deleted file mode 100644 index 9a4670adc..000000000 --- a/lib/plugins/event_processor/forwarding_event_processor.tests.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2021 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import sinon from 'sinon'; - import { createForwardingEventProcessor } from './forwarding_event_processor'; - import * as buildEventV1 from '../../core/event_builder/build_event_v1'; - - describe('lib/plugins/event_processor/forwarding_event_processor', function() { - var sandbox = sinon.sandbox.create(); - var ep; - var dispatcherSpy; - var sendNotificationsSpy; - - beforeEach(() => { - var dispatcher = { - dispatchEvent: () => {}, - }; - var notificationCenter = { - sendNotifications: () => {}, - } - dispatcherSpy = sandbox.spy(dispatcher, 'dispatchEvent'); - sendNotificationsSpy = sandbox.spy(notificationCenter, 'sendNotifications'); - sandbox.stub(buildEventV1, 'formatEvents').returns({ dummy: "event" }); - ep = createForwardingEventProcessor(dispatcher, notificationCenter); - ep.start(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should dispatch event immediately when process is called', () => { - ep.process({ dummy: 'event' }); - sinon.assert.calledWithExactly(dispatcherSpy, { dummy: 'event' }, sinon.match.func); - sinon.assert.calledOnce(sendNotificationsSpy); - }); - - it('should return a resolved promise when stop is called', (done) => { - ep.stop().then(done); - }); - }); diff --git a/lib/plugins/event_processor/index.react_native.ts b/lib/plugins/event_processor/index.react_native.ts index 98a826d25..9481987cb 100644 --- a/lib/plugins/event_processor/index.react_native.ts +++ b/lib/plugins/event_processor/index.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../modules/event_processor/index.react_native'; +import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../event_processor/index.react_native'; export function createEventProcessor( ...args: ConstructorParameters<typeof LogTierV1EventProcessor> diff --git a/lib/plugins/event_processor/index.ts b/lib/plugins/event_processor/index.ts index 70f30e23a..3fc0c3cad 100644 --- a/lib/plugins/event_processor/index.ts +++ b/lib/plugins/event_processor/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../modules/event_processor'; +import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../event_processor'; export function createEventProcessor( ...args: ConstructorParameters<typeof LogTierV1EventProcessor> diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 69c1080d3..8902820eb 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -20,7 +20,7 @@ */ import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; -import { EventProcessor } from './modules/event_processor'; +import { EventProcessor, EventDispatcher } from './event_processor'; import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; import { NOTIFICATION_TYPES } from './utils/enums'; @@ -40,6 +40,8 @@ import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; import { ProjectConfig } from './project_config/project_config'; import { ProjectConfigManager } from './project_config/project_config_manager'; +export { EventDispatcher, EventProcessor } from './event_processor'; + export interface BucketerParams { experimentId: string; experimentKey: string; @@ -143,17 +145,6 @@ export interface Event { params: any; } -export interface EventDispatcher { - /** - * @param event - * Event being submitted for eventual dispatch. - * @param callback - * After the event has at least been queued for dispatch, call this function to return - * control back to the Client. - */ - dispatchEvent: (event: Event, callback: (response: { statusCode: number }) => void) => void; -} - export interface VariationVariable { id: string; value: string; @@ -291,7 +282,7 @@ export interface OptimizelyOptions { datafile?: string | object; datafileManager?: DatafileManager; errorHandler: ErrorHandler; - eventProcessor: EventProcessor; + eventProcessor?: EventProcessor; isValidInstance: boolean; jsonSchemaValidator?: { validate(jsonObject: unknown): boolean; @@ -400,9 +391,9 @@ export type PersistentCacheProvider = () => PersistentCache; * For compatibility with the previous declaration file */ export interface Config extends ConfigLite { - eventBatchSize?: number; // Maximum size of events to be dispatched in a batch - eventFlushInterval?: number; // Maximum time for an event to be enqueued - eventMaxQueueSize?: number; // Maximum size for the event queue + // eventBatchSize?: number; // Maximum size of events to be dispatched in a batch + // eventFlushInterval?: number; // Maximum time for an event to be enqueued + // eventMaxQueueSize?: number; // Maximum size for the event queue sdkKey?: string; odpOptions?: OdpOptions; persistentCacheProvider?: PersistentCacheProvider; @@ -416,8 +407,8 @@ export interface ConfigLite { projectConfigManager: ProjectConfigManager; // errorHandler object for logging error errorHandler?: ErrorHandler; - // event dispatcher function - eventDispatcher?: EventDispatcher; + // event processor + eventProcessor?: EventProcessor; // event dispatcher to use when closing closingEventDispatcher?: EventDispatcher; // The object to validate against the schema diff --git a/lib/tests/mock/mock_repeater.ts b/lib/tests/mock/mock_repeater.ts index 3f330f000..a93cbfa87 100644 --- a/lib/tests/mock/mock_repeater.ts +++ b/lib/tests/mock/mock_repeater.ts @@ -15,32 +15,8 @@ */ import { vi } from 'vitest'; - -import { Repeater } from '../../utils/repeater/repeater'; import { AsyncTransformer } from '../../utils/type'; -export class MockRepeater implements Repeater { - private handler?: AsyncTransformer<number, void>; - - start(): void { - } - - stop(): void { - } - - reset(): void { - } - - setTask(handler: AsyncTransformer<number, void>): void { - this.handler = handler; - } - - pushTick(failureCount: number): void { - this.handler?.(failureCount); - } -} - -//ignore ts no return type error // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const getMockRepeater = () => { const mock = { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 962d06c30..78fd6f907 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2016-2024 Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * https://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2016-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /** * Contains global enums used throughout the library @@ -54,6 +54,7 @@ export const ERROR_MESSAGES = { MISSING_INTEGRATION_KEY: '%s: Integration key missing from datafile. All integrations should include a key.', NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', + NO_EVENT_PROCESSOR: 'No event processor is provided', NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', ODP_CONFIG_NOT_AVAILABLE: '%s: ODP is not integrated to the project.', ODP_EVENT_FAILED: 'ODP event send failed.', diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index 7917f3baa..aa256ef1b 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2017, 2019-2020, 2022-2023, Optimizely + * Copyright 2017, 2019-2020, 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventTags } from '../../modules/event_processor'; +import { EventTags } from '../../event_processor'; import { LoggerFacade } from '../../modules/logging'; import { diff --git a/package-lock.json b/package-lock.json index 2725c62e6..a1588ec80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4966,9 +4966,9 @@ } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "node_modules/@socket.io/component-emitter": { diff --git a/tests/buildEventV1.spec.ts b/tests/buildEventV1.spec.ts index 7f8f56008..dafa67e60 100644 --- a/tests/buildEventV1.spec.ts +++ b/tests/buildEventV1.spec.ts @@ -19,8 +19,8 @@ import { buildConversionEventV1, buildImpressionEventV1, makeBatchedEventV1, -} from '../lib/modules/event_processor/v1/buildEventV1' -import { ImpressionEvent, ConversionEvent } from '../lib/modules/event_processor/events' +} from '../lib/event_processor/v1/buildEventV1' +import { ImpressionEvent, ConversionEvent } from '../lib/event_processor/events' describe('buildEventV1', () => { describe('buildImpressionEventV1', () => { diff --git a/tests/eventQueue.spec.ts b/tests/eventQueue.spec.ts index 0a9e5fae2..f794248dd 100644 --- a/tests/eventQueue.spec.ts +++ b/tests/eventQueue.spec.ts @@ -15,7 +15,7 @@ */ import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; -import { DefaultEventQueue, SingleEventQueue } from '../lib/modules/event_processor/eventQueue' +import { DefaultEventQueue, SingleEventQueue } from '../lib/event_processor/eventQueue' describe('eventQueue', () => { beforeEach(() => { diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 32408ee6f..6f076e614 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -53,7 +53,9 @@ describe('javascript-sdk/react-native', () => { describe('createInstance', () => { const fakeErrorHandler = { handleError: function() {} }; - const fakeEventDispatcher = { dispatchEvent: function() {} }; + const fakeEventDispatcher = { dispatchEvent: async function() { + return Promise.resolve({}); + } }; // @ts-ignore let silentLogger; @@ -84,7 +86,6 @@ describe('javascript-sdk/react-native', () => { const optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, }); @@ -98,7 +99,6 @@ describe('javascript-sdk/react-native', () => { const optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, }); @@ -114,7 +114,6 @@ describe('javascript-sdk/react-native', () => { clientEngine: 'react-sdk', projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, // @ts-ignore logger: silentLogger, }); @@ -166,182 +165,184 @@ describe('javascript-sdk/react-native', () => { }); }); - describe('event processor configuration', () => { - // @ts-ignore - let eventProcessorSpy; - beforeEach(() => { - eventProcessorSpy = vi.spyOn(eventProcessor, 'createEventProcessor'); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should use default event flush interval when none is provided', () => { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - }); - - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 1000, - }) - ); - }); - - describe('with an invalid flush interval', () => { - beforeEach(() => { - vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should ignore the event flush interval and use the default instead', () => { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - // @ts-ignore - eventFlushInterval: ['invalid', 'flush', 'interval'], - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 1000, - }) - ); - }); - }); - - describe('with a valid flush interval', () => { - beforeEach(() => { - vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should use the provided event flush interval', () => { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - eventFlushInterval: 9000, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 9000, - }) - ); - }); - }); - - it('should use default event batch size when none is provided', () => { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 10, - }) - ); - }); - - describe('with an invalid event batch size', () => { - beforeEach(() => { - vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should ignore the event batch size and use the default instead', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - // @ts-ignore - eventBatchSize: null, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 10, - }) - ); - }); - }); - - describe('with a valid event batch size', () => { - beforeEach(() => { - vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should use the provided event batch size', () => { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - eventBatchSize: 300, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 300, - }) - ); - }); - }); - }); + // TODO: user will create and inject an event processor + // these tests will be refactored accordingly + // describe('event processor configuration', () => { + // // @ts-ignore + // let eventProcessorSpy; + // beforeEach(() => { + // eventProcessorSpy = vi.spyOn(eventProcessor, 'createEventProcessor'); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should use default event flush interval when none is provided', () => { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // // @ts-ignore + // logger: silentLogger, + // }); + + // expect( + // // @ts-ignore + // eventProcessorSpy + // ).toBeCalledWith( + // expect.objectContaining({ + // flushInterval: 1000, + // }) + // ); + // }); + + // describe('with an invalid flush interval', () => { + // beforeEach(() => { + // vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should ignore the event flush interval and use the default instead', () => { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // // @ts-ignore + // logger: silentLogger, + // // @ts-ignore + // eventFlushInterval: ['invalid', 'flush', 'interval'], + // }); + // expect( + // // @ts-ignore + // eventProcessorSpy + // ).toBeCalledWith( + // expect.objectContaining({ + // flushInterval: 1000, + // }) + // ); + // }); + // }); + + // describe('with a valid flush interval', () => { + // beforeEach(() => { + // vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should use the provided event flush interval', () => { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // // @ts-ignore + // logger: silentLogger, + // eventFlushInterval: 9000, + // }); + // expect( + // // @ts-ignore + // eventProcessorSpy + // ).toBeCalledWith( + // expect.objectContaining({ + // flushInterval: 9000, + // }) + // ); + // }); + // }); + + // it('should use default event batch size when none is provided', () => { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // // @ts-ignore + // logger: silentLogger, + // }); + // expect( + // // @ts-ignore + // eventProcessorSpy + // ).toBeCalledWith( + // expect.objectContaining({ + // batchSize: 10, + // }) + // ); + // }); + + // describe('with an invalid event batch size', () => { + // beforeEach(() => { + // vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should ignore the event batch size and use the default instead', () => { + // optimizelyFactory.createInstance({ + // datafile: testData.getTestProjectConfigWithFeatures(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // // @ts-ignore + // logger: silentLogger, + // // @ts-ignore + // eventBatchSize: null, + // }); + // expect( + // // @ts-ignore + // eventProcessorSpy + // ).toBeCalledWith( + // expect.objectContaining({ + // batchSize: 10, + // }) + // ); + // }); + // }); + + // describe('with a valid event batch size', () => { + // beforeEach(() => { + // vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should use the provided event batch size', () => { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // // @ts-ignore + // logger: silentLogger, + // eventBatchSize: 300, + // }); + // expect( + // // @ts-ignore + // eventProcessorSpy + // ).toBeCalledWith( + // expect.objectContaining({ + // batchSize: 300, + // }) + // ); + // }); + // }); + // }); }); }); }); diff --git a/tests/pendingEventsDispatcher.spec.ts b/tests/pendingEventsDispatcher.spec.ts index 153edae5e..d39b58e22 100644 --- a/tests/pendingEventsDispatcher.spec.ts +++ b/tests/pendingEventsDispatcher.spec.ts @@ -29,20 +29,28 @@ import { LocalStoragePendingEventsDispatcher, PendingEventsDispatcher, DispatcherEntry, -} from '../lib/modules/event_processor/pendingEventsDispatcher' -import { EventDispatcher, EventV1Request } from '../lib/modules/event_processor/eventDispatcher' -import { EventV1 } from '../lib/modules/event_processor/v1/buildEventV1' -import { PendingEventsStore, LocalStorageStore } from '../lib/modules/event_processor/pendingEventsStore' +} from '../lib/event_processor/pendingEventsDispatcher' +import { EventDispatcher, EventDispatcherResponse, EventV1Request } from '../lib/event_processor/eventDispatcher' +import { EventV1 } from '../lib/event_processor/v1/buildEventV1' +import { PendingEventsStore, LocalStorageStore } from '../lib/event_processor/pendingEventsStore' import { uuid, getTimestamp } from '../lib/utils/fns' +import { resolvablePromise, ResolvablePromise } from '../lib/utils/promise/resolvablePromise'; describe('LocalStoragePendingEventsDispatcher', () => { let originalEventDispatcher: EventDispatcher let pendingEventsDispatcher: PendingEventsDispatcher + let eventDispatcherResponses: Array<ResolvablePromise<EventDispatcherResponse>> beforeEach(() => { + eventDispatcherResponses = []; originalEventDispatcher = { - dispatchEvent: vi.fn(), + dispatchEvent: vi.fn().mockImplementation(() => { + const response = resolvablePromise<EventDispatcherResponse>() + eventDispatcherResponses.push(response) + return response.promise + }), } + pendingEventsDispatcher = new LocalStoragePendingEventsDispatcher({ eventDispatcher: originalEventDispatcher, }) @@ -54,54 +62,44 @@ describe('LocalStoragePendingEventsDispatcher', () => { localStorage.clear() }) - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', () => { - const callback = vi.fn() + it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', async () => { const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', params: ({ id: 'event' } as unknown) as EventV1, } - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) + pendingEventsDispatcher.dispatchEvent(eventV1Request) + + eventDispatcherResponses[0].resolve({ statusCode: 200 }) - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] - internalDispatchCall[1]({ statusCode: 200 }) - - // assert that the original dispatch function was called with the request + + // assert that the original dispatch function was called with the request expect((originalEventDispatcher.dispatchEvent as unknown) as MockInstance).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) }) it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => { - const callback = vi.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', params: ({ id: 'event' } as unknown) as EventV1, } - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) + pendingEventsDispatcher.dispatchEvent(eventV1Request) + + eventDispatcherResponses[0].resolve({ statusCode: 400 }) - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) + + eventDispatcherResponses[0].resolve({ statusCode: 400 }) // assert that the original dispatch function was called with the request expect((originalEventDispatcher.dispatchEvent as unknown) as MockInstance).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400}) }) }) @@ -109,11 +107,19 @@ describe('PendingEventsDispatcher', () => { let originalEventDispatcher: EventDispatcher let pendingEventsDispatcher: PendingEventsDispatcher let store: PendingEventsStore<DispatcherEntry> + let eventDispatcherResponses: Array<ResolvablePromise<EventDispatcherResponse>> beforeEach(() => { + eventDispatcherResponses = []; + originalEventDispatcher = { - dispatchEvent: vi.fn(), + dispatchEvent: vi.fn().mockImplementation(() => { + const response = resolvablePromise<EventDispatcherResponse>() + eventDispatcherResponses.push(response) + return response.promise + }), } + store = new LocalStorageStore({ key: 'test', maxValues: 3, @@ -132,15 +138,14 @@ describe('PendingEventsDispatcher', () => { describe('dispatch', () => { describe('when the dispatch is successful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = vi.fn() + it('should save the pendingEvent to the store and remove it once dispatch is completed', async () => { const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', params: ({ id: 'event' } as unknown) as EventV1, } - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) + pendingEventsDispatcher.dispatchEvent(eventV1Request) expect(store.values()).toHaveLength(1) expect(store.get('uuid')).toEqual({ @@ -148,12 +153,12 @@ describe('PendingEventsDispatcher', () => { timestamp: 1, request: eventV1Request, }) - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback + eventDispatcherResponses[0].resolve({ statusCode: 200 }) + await eventDispatcherResponses[0].promise + const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] - const internalCallback = internalDispatchCall[1]({ statusCode: 200 }) // assert that the original dispatch function was called with the request expect( @@ -161,24 +166,19 @@ describe('PendingEventsDispatcher', () => { ).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - expect(store.values()).toHaveLength(0) }) }) describe('when the dispatch is unsuccessful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = vi.fn() + it('should save the pendingEvent to the store and remove it once dispatch is completed', async () => { const eventV1Request: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', params: ({ id: 'event' } as unknown) as EventV1, } - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) + pendingEventsDispatcher.dispatchEvent(eventV1Request) expect(store.values()).toHaveLength(1) expect(store.get('uuid')).toEqual({ @@ -186,23 +186,20 @@ describe('PendingEventsDispatcher', () => { timestamp: 1, request: eventV1Request, }) - expect(callback).not.toHaveBeenCalled() + + eventDispatcherResponses[0].resolve({ statusCode: 400 }) + await eventDispatcherResponses[0].promise // manually invoke original eventDispatcher callback const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) - + // assert that the original dispatch function was called with the request expect( (originalEventDispatcher.dispatchEvent as unknown) as MockInstance, ).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400 }) - expect(store.values()).toHaveLength(0) }) }) @@ -219,10 +216,9 @@ describe('PendingEventsDispatcher', () => { }) describe('when there are multiple pending events in the store', () => { - it('should dispatch all of the pending events, and remove them from store', () => { + it('should dispatch all of the pending events, and remove them from store', async () => { expect(store.values()).toHaveLength(0) - const callback = vi.fn() const eventV1Request1: EventV1Request = { url: 'http://cdn.com/', httpVerb: 'POST', @@ -251,12 +247,9 @@ describe('PendingEventsDispatcher', () => { pendingEventsDispatcher.sendPendingEvents() expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) - // manually invoke original eventDispatcher callback - const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) - .mock.calls - internalDispatchCalls[0][1]({ statusCode: 200 }) - internalDispatchCalls[1][1]({ statusCode: 200 }) - + eventDispatcherResponses[0].resolve({ statusCode: 200 }) + eventDispatcherResponses[1].resolve({ statusCode: 200 }) + await Promise.all([eventDispatcherResponses[0].promise, eventDispatcherResponses[1].promise]) expect(store.values()).toHaveLength(0) }) }) diff --git a/tests/pendingEventsStore.spec.ts b/tests/pendingEventsStore.spec.ts index 9a3fff864..9c255b118 100644 --- a/tests/pendingEventsStore.spec.ts +++ b/tests/pendingEventsStore.spec.ts @@ -15,7 +15,7 @@ */ import { describe, beforeEach, afterEach, it, expect, vi, MockInstance } from 'vitest'; -import { LocalStorageStore } from '../lib/modules/event_processor/pendingEventsStore' +import { LocalStorageStore } from '../lib/event_processor/pendingEventsStore' type TestEntry = { uuid: string diff --git a/tests/reactNativeEventsStore.spec.ts b/tests/reactNativeEventsStore.spec.ts index 0c211309f..d7155a629 100644 --- a/tests/reactNativeEventsStore.spec.ts +++ b/tests/reactNativeEventsStore.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,8 +54,7 @@ vi.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; -import { ReactNativeEventsStore } from '../lib/modules/event_processor/reactNativeEventsStore' -import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache' +import { ReactNativeEventsStore } from '../lib/event_processor/reactNativeEventsStore' const STORE_KEY = 'test-store' diff --git a/tests/reactNativeV1EventProcessor.spec.ts b/tests/reactNativeV1EventProcessor.spec.ts index a7bf8a8f5..995dd6024 100644 --- a/tests/reactNativeV1EventProcessor.spec.ts +++ b/tests/reactNativeV1EventProcessor.spec.ts @@ -17,11 +17,11 @@ import { describe, beforeEach, it, vi, expect } from 'vitest'; vi.mock('@react-native-community/netinfo'); -vi.mock('../lib/modules/event_processor/reactNativeEventsStore'); +vi.mock('../lib/event_processor/reactNativeEventsStore'); -import { ReactNativeEventsStore } from '../lib/modules/event_processor/reactNativeEventsStore'; +import { ReactNativeEventsStore } from '../lib/event_processor/reactNativeEventsStore'; import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; -import { LogTierV1EventProcessor } from '../lib/modules/event_processor/index.react_native'; +import { LogTierV1EventProcessor } from '../lib/event_processor/index.react_native'; import { PersistentCacheProvider } from '../lib/shared_types'; describe('LogTierV1EventProcessor', () => { @@ -58,7 +58,7 @@ describe('LogTierV1EventProcessor', () => { const noop = () => {}; new LogTierV1EventProcessor({ - dispatcher: { dispatchEvent: () => {} }, + dispatcher: { dispatchEvent: () => Promise.resolve({}) }, persistentCacheProvider: fakePersistentCacheProvider, }) diff --git a/tests/requestTracker.spec.ts b/tests/requestTracker.spec.ts index 37245835c..10c042a66 100644 --- a/tests/requestTracker.spec.ts +++ b/tests/requestTracker.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ import { describe, it, expect } from 'vitest'; -import RequestTracker from '../lib/modules/event_processor/requestTracker' +import RequestTracker from '../lib/event_processor/requestTracker' describe('requestTracker', () => { describe('onRequestsComplete', () => { diff --git a/tests/sendBeaconDispatcher.spec.ts b/tests/sendBeaconDispatcher.spec.ts index 2b67268d3..3b69ffc27 100644 --- a/tests/sendBeaconDispatcher.spec.ts +++ b/tests/sendBeaconDispatcher.spec.ts @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { describe, afterEach, it, expect } from 'vitest'; +import { describe, beforeEach, it, expect, vi, MockInstance } from 'vitest'; import sendBeaconDispatcher, { Event } from '../lib/plugins/event_dispatcher/send_beacon_dispatcher'; -import { anyString, anything, capture, instance, mock, reset, when } from 'ts-mockito'; describe('dispatchEvent', function() { - const mockNavigator = mock<Navigator>(); + let sendBeaconSpy: MockInstance<typeof navigator.sendBeacon>; - afterEach(function() { - reset(mockNavigator); + beforeEach(() => { + sendBeaconSpy = vi.fn(); + navigator.sendBeacon = sendBeaconSpy as any; }); it('should call sendBeacon with correct url, data and type', async () => { @@ -33,13 +33,11 @@ describe('dispatchEvent', function() { params: eventParams, }; - when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(true); - const navigator = instance(mockNavigator); - global.navigator.sendBeacon = navigator.sendBeacon; + sendBeaconSpy.mockReturnValue(true); - sendBeaconDispatcher.dispatchEvent(eventObj, () => {}); + sendBeaconDispatcher.dispatchEvent(eventObj) - const [url, data] = capture(mockNavigator.sendBeacon).last(); + const [url, data] = sendBeaconSpy.mock.calls[0]; const blob = data as Blob; const reader = new FileReader(); @@ -57,51 +55,27 @@ describe('dispatchEvent', function() { expect(sentParams).toEqual(JSON.stringify(eventObj.params)); }); - it('should call call callback with status 200 on sendBeacon success', () => - new Promise<void>((pass, fail) => { - var eventParams = { testParam: 'testParamValue' }; - var eventObj: Event = { - url: 'https://cdn.com/event', - httpVerb: 'POST', - params: eventParams, - }; - - when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(true); - const navigator = instance(mockNavigator); - global.navigator.sendBeacon = navigator.sendBeacon; - - sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { - try { - expect(res.statusCode).toEqual(200); - pass(); - } catch(err) { - fail(err); - } - }); - }) - ); + it('should resolve the response on sendBeacon success', async () => { + const eventParams = { testParam: 'testParamValue' }; + const eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; - it('should call call callback with status 200 on sendBeacon failure', () => - new Promise<void>((pass, fail) => { - var eventParams = { testParam: 'testParamValue' }; - var eventObj: Event = { - url: 'https://cdn.com/event', - httpVerb: 'POST', - params: eventParams, - }; - - when(mockNavigator.sendBeacon(anyString(), anything())).thenReturn(false); - const navigator = instance(mockNavigator); - global.navigator.sendBeacon = navigator.sendBeacon; - - sendBeaconDispatcher.dispatchEvent(eventObj, (res) => { - try { - expect(res.statusCode).toEqual(500); - pass(); - } catch(err) { - fail(err); - } - }); - }) - ); + sendBeaconSpy.mockReturnValue(true); + await expect(sendBeaconDispatcher.dispatchEvent(eventObj)).resolves.not.toThrow(); + }); + + it('should reject the response on sendBeacon success', async () => { + const eventParams = { testParam: 'testParamValue' }; + const eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + sendBeaconSpy.mockReturnValue(false); + await expect(sendBeaconDispatcher.dispatchEvent(eventObj)).rejects.toThrow(); + }); }); diff --git a/tests/v1EventProcessor.react_native.spec.ts b/tests/v1EventProcessor.react_native.spec.ts index 7722ef30c..d0fccc4b0 100644 --- a/tests/v1EventProcessor.react_native.spec.ts +++ b/tests/v1EventProcessor.react_native.spec.ts @@ -21,17 +21,18 @@ vi.mock('@react-native-async-storage/async-storage'); import { NotificationSender } from '../lib/core/notification_center' import { NOTIFICATION_TYPES } from '../lib/utils/enums' -import { LogTierV1EventProcessor } from '../lib/modules/event_processor/v1/v1EventProcessor.react_native' +import { LogTierV1EventProcessor } from '../lib/event_processor/v1/v1EventProcessor.react_native' import { EventDispatcher, EventV1Request, - EventDispatcherCallback, -} from '../lib/modules/event_processor/eventDispatcher' -import { EventProcessor, ProcessableEvent } from '../lib/modules/event_processor/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/modules/event_processor/v1/buildEventV1' + EventDispatcherResponse, +} from '../lib/event_processor/eventDispatcher' +import { EventProcessor, ProcessableEvent } from '../lib/event_processor/eventProcessor' +import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/event_processor/v1/buildEventV1' import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' import { triggerInternetState } from '../__mocks__/@react-native-community/netinfo' -import { DefaultEventQueue } from '../lib/modules/event_processor/eventQueue' +import { DefaultEventQueue } from '../lib/event_processor/eventQueue' +import { resolvablePromise, ResolvablePromise } from '../lib/utils/promise/resolvablePromise'; function createImpressionEvent() { return { @@ -118,13 +119,10 @@ describe('LogTierV1EventProcessorReactNative', () => { let dispatchStub: Mock beforeEach(() => { - dispatchStub = vi.fn() + dispatchStub = vi.fn().mockResolvedValue({ statusCode: 200 }) stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, + dispatchEvent: dispatchStub, } }) @@ -134,12 +132,13 @@ describe('LogTierV1EventProcessorReactNative', () => { }) describe('stop()', () => { - let localCallback: EventDispatcherCallback + let resolvableResponse: ResolvablePromise<EventDispatcherResponse> beforeEach(async () => { stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) - localCallback = callback + resolvableResponse = resolvablePromise<EventDispatcherResponse>() + return resolvableResponse.promise }, } }) @@ -167,18 +166,19 @@ describe('LogTierV1EventProcessorReactNative', () => { processor.process(impressionEvent) await new Promise(resolve => setTimeout(resolve, 150)) - // @ts-ignore - localCallback({ statusCode: 200 }) + + resolvableResponse.resolve({ statusCode: 200 }) }) it('should return a promise that is resolved when the dispatcher callback returns a 400 response', async () => { // This test is saying that even if the request fails to send but // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any + let responsePromise: ResolvablePromise<EventDispatcherResponse> stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { dispatchStub(event) - localCallback = callback + responsePromise = resolvablePromise<EventDispatcherResponse>() + return responsePromise.promise; }, } @@ -194,14 +194,14 @@ describe('LogTierV1EventProcessorReactNative', () => { await new Promise(resolve => setTimeout(resolve, 150)) - localCallback({ statusCode: 400 }) + resolvableResponse.resolve({ statusCode: 400 }) }) it('should return a promise when multiple event batches are sent', async () => { stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) }, } @@ -226,8 +226,10 @@ describe('LogTierV1EventProcessorReactNative', () => { it('should stop accepting events after stop is called', async () => { const dispatcher = { - dispatchEvent: vi.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) + dispatchEvent: vi.fn((event: EventV1Request) => { + return new Promise<EventDispatcherResponse>(resolve => { + setTimeout(() => resolve({ statusCode: 204 }), 0) + }) }) } const processor = new LogTierV1EventProcessor({ @@ -405,13 +407,17 @@ describe('LogTierV1EventProcessorReactNative', () => { processor.process(createImpressionEvent()) // flushing should reset queue, at this point only has two events expect(dispatchStub).toHaveBeenCalledTimes(1) + + // clear the async storate cache to ensure next tests + // works correctly + await new Promise(resolve => setTimeout(resolve, 400)) }) }) describe('when a notification center is provided', () => { it('should trigger a notification when the event dispatcher dispatches an event', async () => { const dispatcher: EventDispatcher = { - dispatchEvent: vi.fn() + dispatchEvent: vi.fn().mockResolvedValue({ statusCode: 200 }) } const notificationCenter: NotificationSender = { @@ -425,8 +431,8 @@ describe('LogTierV1EventProcessorReactNative', () => { }) await processor.start() - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) + const impressionEvent = createImpressionEvent() + processor.process(impressionEvent) await new Promise(resolve => setTimeout(resolve, 150)) expect(notificationCenter.sendNotifications).toBeCalledTimes(1) @@ -486,9 +492,9 @@ describe('LogTierV1EventProcessorReactNative', () => { let receivedEvents: EventV1Request[] = [] stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) - callback({ statusCode: 400 }) + return Promise.resolve({ statusCode: 400 }) }, } @@ -523,10 +529,10 @@ describe('LogTierV1EventProcessorReactNative', () => { receivedEvents = [] stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { receivedEvents.push(event) dispatchStub(event) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) }, } @@ -549,9 +555,9 @@ describe('LogTierV1EventProcessorReactNative', () => { it('should process all the events left in buffer when the app closed last time', async () => { stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) }, } @@ -579,10 +585,10 @@ describe('LogTierV1EventProcessorReactNative', () => { let receivedEvents: EventV1Request[] = [] stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { receivedEvents.push(event) dispatchStub(event) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) }, } @@ -608,9 +614,9 @@ describe('LogTierV1EventProcessorReactNative', () => { it('should dispatch pending events first and then process events in buffer store', async () => { stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) - callback({ statusCode: 400 }) + return Promise.resolve({ statusCode: 400 }) }, } @@ -639,10 +645,10 @@ describe('LogTierV1EventProcessorReactNative', () => { const visitorIds: string[] = [] stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) event.params.visitors.forEach(visitor => visitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) }, } @@ -667,14 +673,14 @@ describe('LogTierV1EventProcessorReactNative', () => { let receivedVisitorIds: string[] = [] let dispatchCount = 0 stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) dispatchCount++ if (dispatchCount > 4) { event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) } else { - callback({ statusCode: 400 }) + return Promise.resolve({ statusCode: 400 }) } }, } @@ -722,11 +728,11 @@ describe('LogTierV1EventProcessorReactNative', () => { let receivedVisitorIds: string[] = [] let dispatchCount = 0 stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) dispatchCount++ event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 400 }) + return Promise.resolve({ statusCode: 400 }) }, } @@ -775,14 +781,14 @@ describe('LogTierV1EventProcessorReactNative', () => { let receivedVisitorIds: string[] = [] let dispatchCount = 0 stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) dispatchCount++ if (dispatchCount > 4) { event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) } else { - callback({ statusCode: 400 }) + return Promise.resolve({ statusCode: 400 }) } }, } @@ -827,14 +833,14 @@ describe('LogTierV1EventProcessorReactNative', () => { let receivedVisitorIds: string[] = [] let dispatchCount = 0 stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request) { dispatchStub(event) dispatchCount++ if (dispatchCount > 4) { event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) } else { - callback({ statusCode: 400 }) + return Promise.resolve({ statusCode: 400 }) } }, } diff --git a/tests/v1EventProcessor.spec.ts b/tests/v1EventProcessor.spec.ts index 0649bad72..bd7333bee 100644 --- a/tests/v1EventProcessor.spec.ts +++ b/tests/v1EventProcessor.spec.ts @@ -15,16 +15,17 @@ */ import { describe, beforeEach, afterEach, it, vi, expect, Mock } from 'vitest'; -import { LogTierV1EventProcessor } from '../lib/modules/event_processor/v1/v1EventProcessor' +import { LogTierV1EventProcessor } from '../lib/event_processor/v1/v1EventProcessor' import { EventDispatcher, EventV1Request, - EventDispatcherCallback, -} from '../lib/modules/event_processor/eventDispatcher' -import { EventProcessor } from '../lib/modules/event_processor/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/modules/event_processor/v1/buildEventV1' + EventDispatcherResponse, +} from '../lib/event_processor/eventDispatcher' +import { EventProcessor } from '../lib/event_processor/eventProcessor' +import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/event_processor/v1/buildEventV1' import { NotificationCenter, NotificationSender } from '../lib/core/notification_center' import { NOTIFICATION_TYPES } from '../lib/utils/enums' +import { resolvablePromise, ResolvablePromise } from '../lib/utils/promise/resolvablePromise'; function createImpressionEvent() { return { @@ -118,9 +119,9 @@ describe('LogTierV1EventProcessor', () => { dispatchStub = vi.fn() stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { dispatchStub(event) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) }, } }) @@ -130,12 +131,19 @@ describe('LogTierV1EventProcessor', () => { }) describe('stop()', () => { - let localCallback: EventDispatcherCallback + let resposePromise: ResolvablePromise<EventDispatcherResponse> beforeEach(() => { stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { dispatchStub(event) - localCallback = callback + return Promise.resolve({ statusCode: 200 }) + }, + } + stubDispatcher = { + dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { + dispatchStub(event) + resposePromise = resolvablePromise() + return resposePromise.promise }, } }) @@ -170,7 +178,7 @@ describe('LogTierV1EventProcessor', () => { done() }) - localCallback({ statusCode: 200 }) + resposePromise.resolve({ statusCode: 200 }) }) ) @@ -178,11 +186,11 @@ describe('LogTierV1EventProcessor', () => { new Promise<void>((done) => { // This test is saying that even if the request fails to send but // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { dispatchStub(event) - localCallback = callback + resposePromise = resolvablePromise() + return Promise.resolve({statusCode: 400}) }, } @@ -199,19 +207,15 @@ describe('LogTierV1EventProcessor', () => { processor.stop().then(() => { done() }) - - localCallback({ - statusCode: 400, - }) }) ) it('should return a promise when multiple event batches are sent', () => new Promise<void>((done) => { stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { + dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { dispatchStub(event) - callback({ statusCode: 200 }) + return Promise.resolve({ statusCode: 200 }) }, } @@ -237,8 +241,10 @@ describe('LogTierV1EventProcessor', () => { it('should stop accepting events after stop is called', () => { const dispatcher = { - dispatchEvent: vi.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) + dispatchEvent: vi.fn((event: EventV1Request) => { + return new Promise<EventDispatcherResponse>((resolve) => { + setTimeout(() => resolve({ statusCode: 204 }), 0) + }) }) } const processor = new LogTierV1EventProcessor({ @@ -271,10 +277,12 @@ describe('LogTierV1EventProcessor', () => { }) it('should resolve the stop promise after all dispatcher requests are done', async () => { - const dispatchCbs: Array<EventDispatcherCallback> = [] + const dispatchPromises: Array<ResolvablePromise<EventDispatcherResponse>> = [] const dispatcher = { - dispatchEvent: vi.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - dispatchCbs.push(callback) + dispatchEvent: vi.fn((event: EventV1Request) => { + const response = resolvablePromise<EventDispatcherResponse>(); + dispatchPromises.push(response); + return response.promise; }) } @@ -288,7 +296,7 @@ describe('LogTierV1EventProcessor', () => { for (let i = 0; i < 4; i++) { processor.process(createImpressionEvent()) } - expect(dispatchCbs.length).toBe(2) + expect(dispatchPromises.length).toBe(2) let stopPromiseResolved = false const stopPromise = processor.stop().then(() => { @@ -296,10 +304,10 @@ describe('LogTierV1EventProcessor', () => { }) expect(stopPromiseResolved).toBe(false) - dispatchCbs[0]({ statusCode: 204 }) + dispatchPromises[0].resolve({ statusCode: 204 }) vi.advanceTimersByTime(100) expect(stopPromiseResolved).toBe(false) - dispatchCbs[1]({ statusCode: 204 }) + dispatchPromises[1].resolve({ statusCode: 204 }) await stopPromise expect(stopPromiseResolved).toBe(true) }) @@ -500,9 +508,9 @@ describe('LogTierV1EventProcessor', () => { }) describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', () => { + it('should trigger a notification when the event dispatcher dispatches an event', async () => { const dispatcher: EventDispatcher = { - dispatchEvent: vi.fn() + dispatchEvent: vi.fn().mockResolvedValue({ statusCode: 200 }) } const notificationCenter: NotificationSender = { @@ -514,7 +522,7 @@ describe('LogTierV1EventProcessor', () => { notificationCenter, batchSize: 1, }) - processor.start() + await processor.start() const impressionEvent1 = createImpressionEvent() processor.process(impressionEvent1) From af9fcab74e29d8fe4a7d373b02b7186f57ebf410 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 22 Nov 2024 02:50:45 +0600 Subject: [PATCH 093/200] [FSSDK-10642] Refactor batch event processor (#960) --- lib/core/event_builder/build_event_v1.ts | 2 +- lib/core/event_builder/index.ts | 2 +- ...batch_event_processor.react_native.spec.ts | 171 +++ .../batch_event_processor.react_native.ts | 55 + .../batch_event_processor.spec.ts | 1223 +++++++++++++++++ lib/event_processor/batch_event_processor.ts | 271 ++++ .../default_dispatcher.browser.ts | 2 +- .../default_dispatcher.node.ts | 2 +- lib/event_processor/default_dispatcher.ts | 2 +- lib/event_processor/eventProcessor.ts | 69 +- lib/event_processor/eventQueue.ts | 162 --- .../event_processor_factory.browser.spec.ts | 130 +- .../event_processor_factory.browser.ts | 32 + .../event_processor_factory.node.spec.ts | 151 +- .../event_processor_factory.node.ts | 20 + ...ent_processor_factory.react_native.spec.ts | 201 ++- .../event_processor_factory.react_native.ts | 40 + .../event_processor_factory.spec.ts | 317 +++++ .../event_processor_factory.ts | 123 ++ .../forwarding_event_processor.spec.ts | 138 +- .../forwarding_event_processor.ts | 63 +- lib/event_processor/index.react_native.ts | 23 - lib/event_processor/index.ts | 23 - lib/event_processor/managed.ts | 20 - .../pendingEventsDispatcher.ts | 86 -- lib/event_processor/pendingEventsStore.ts | 117 -- lib/event_processor/reactNativeEventsStore.ts | 84 -- lib/event_processor/requestTracker.ts | 60 - lib/event_processor/synchronizer.ts | 42 - .../v1/v1EventProcessor.react_native.ts | 250 ---- lib/event_processor/v1/v1EventProcessor.ts | 117 -- lib/index.browser.tests.js | 8 - lib/index.browser.ts | 7 +- lib/index.node.tests.js | 23 +- lib/index.node.ts | 6 +- lib/index.react_native.ts | 6 +- lib/optimizely/index.tests.js | 888 ++++++------ lib/optimizely/index.ts | 17 +- lib/optimizely_user_context/index.tests.js | 116 +- .../send_beacon_dispatcher.ts | 2 +- lib/plugins/event_processor/index.ts | 25 - .../polling_datafile_manager.ts | 5 - lib/project_config/project_config_manager.ts | 5 - lib/service.spec.ts | 34 +- lib/service.ts | 31 +- lib/shared_types.ts | 7 +- lib/tests/mock/create_event.ts | 57 + lib/tests/mock/mock_cache.ts | 95 ++ .../async_storage_cache.react_native.spec.ts | 113 ++ .../cache/async_storage_cache.react_native.ts | 49 + lib/utils/cache/cache.spec.ts | 351 +++++ lib/utils/cache/cache.ts | 154 +++ .../cache/local_storage_cache.browser.spec.ts | 85 ++ .../cache/local_storage_cache.browser.ts | 54 + .../index.tests.js | 74 - .../event_processor_config_validator/index.ts | 45 - lib/utils/event_tag_utils/index.ts | 2 +- .../executor/backoff_retry_runner.spec.ts | 139 ++ lib/utils/executor/backoff_retry_runner.ts | 52 + lib/utils/http_request_handler/http_util.ts | 4 + .../id_generator/index.ts} | 23 +- .../@react-native-community/netinfo.ts | 38 + lib/utils/repeater/repeater.spec.ts | 1 - lib/utils/repeater/repeater.ts | 2 +- lib/utils/type.ts | 5 +- tests/eventQueue.spec.ts | 290 ---- tests/index.react_native.spec.ts | 2 - tests/pendingEventsDispatcher.spec.ts | 257 ---- tests/pendingEventsStore.spec.ts | 143 -- tests/reactNativeEventsStore.spec.ts | 351 ----- tests/reactNativeV1EventProcessor.spec.ts | 69 - tests/requestTracker.spec.ts | 65 - tests/v1EventProcessor.react_native.spec.ts | 891 ------------ tests/v1EventProcessor.spec.ts | 582 -------- 74 files changed, 4650 insertions(+), 4521 deletions(-) create mode 100644 lib/event_processor/batch_event_processor.react_native.spec.ts create mode 100644 lib/event_processor/batch_event_processor.react_native.ts create mode 100644 lib/event_processor/batch_event_processor.spec.ts create mode 100644 lib/event_processor/batch_event_processor.ts delete mode 100644 lib/event_processor/eventQueue.ts create mode 100644 lib/event_processor/event_processor_factory.spec.ts create mode 100644 lib/event_processor/event_processor_factory.ts delete mode 100644 lib/event_processor/index.react_native.ts delete mode 100644 lib/event_processor/index.ts delete mode 100644 lib/event_processor/managed.ts delete mode 100644 lib/event_processor/pendingEventsDispatcher.ts delete mode 100644 lib/event_processor/pendingEventsStore.ts delete mode 100644 lib/event_processor/reactNativeEventsStore.ts delete mode 100644 lib/event_processor/requestTracker.ts delete mode 100644 lib/event_processor/synchronizer.ts delete mode 100644 lib/event_processor/v1/v1EventProcessor.react_native.ts delete mode 100644 lib/event_processor/v1/v1EventProcessor.ts delete mode 100644 lib/plugins/event_processor/index.ts create mode 100644 lib/tests/mock/create_event.ts create mode 100644 lib/tests/mock/mock_cache.ts create mode 100644 lib/utils/cache/async_storage_cache.react_native.spec.ts create mode 100644 lib/utils/cache/async_storage_cache.react_native.ts create mode 100644 lib/utils/cache/cache.spec.ts create mode 100644 lib/utils/cache/cache.ts create mode 100644 lib/utils/cache/local_storage_cache.browser.spec.ts create mode 100644 lib/utils/cache/local_storage_cache.browser.ts delete mode 100644 lib/utils/event_processor_config_validator/index.tests.js delete mode 100644 lib/utils/event_processor_config_validator/index.ts create mode 100644 lib/utils/executor/backoff_retry_runner.spec.ts create mode 100644 lib/utils/executor/backoff_retry_runner.ts create mode 100644 lib/utils/http_request_handler/http_util.ts rename lib/{plugins/event_processor/index.react_native.ts => utils/id_generator/index.ts} (50%) create mode 100644 lib/utils/import.react_native/@react-native-community/netinfo.ts delete mode 100644 tests/eventQueue.spec.ts delete mode 100644 tests/pendingEventsDispatcher.spec.ts delete mode 100644 tests/pendingEventsStore.spec.ts delete mode 100644 tests/reactNativeEventsStore.spec.ts delete mode 100644 tests/reactNativeV1EventProcessor.spec.ts delete mode 100644 tests/requestTracker.spec.ts delete mode 100644 tests/v1EventProcessor.react_native.spec.ts delete mode 100644 tests/v1EventProcessor.spec.ts diff --git a/lib/core/event_builder/build_event_v1.ts b/lib/core/event_builder/build_event_v1.ts index 1ca9c63ea..0479dc79a 100644 --- a/lib/core/event_builder/build_event_v1.ts +++ b/lib/core/event_builder/build_event_v1.ts @@ -17,7 +17,7 @@ import { EventTags, ConversionEvent, ImpressionEvent, -} from '../../event_processor'; +} from '../../event_processor/events'; import { Event } from '../../shared_types'; diff --git a/lib/core/event_builder/index.ts b/lib/core/event_builder/index.ts index 707cb178c..20efd53c7 100644 --- a/lib/core/event_builder/index.ts +++ b/lib/core/event_builder/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { LoggerFacade } from '../../modules/logging'; -import { EventV1 as CommonEventParams } from '../../event_processor'; +import { EventV1 as CommonEventParams } from '../../event_processor/v1/buildEventV1'; import fns from '../../utils/fns'; import { CONTROL_ATTRIBUTES, RESERVED_EVENT_KEYWORDS } from '../../utils/enums'; diff --git a/lib/event_processor/batch_event_processor.react_native.spec.ts b/lib/event_processor/batch_event_processor.react_native.spec.ts new file mode 100644 index 000000000..68ccd6016 --- /dev/null +++ b/lib/event_processor/batch_event_processor.react_native.spec.ts @@ -0,0 +1,171 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +const mockNetInfo = vi.hoisted(() => { + const netInfo = { + listeners: [], + unsubs: [], + addEventListener(fn: any) { + this.listeners.push(fn); + const unsub = vi.fn(); + this.unsubs.push(unsub); + return unsub; + }, + pushState(state: boolean) { + for (const listener of this.listeners) { + listener({ isInternetReachable: state }); + } + }, + clear() { + this.listeners = []; + this.unsubs = []; + } + }; + return netInfo; +}); + +vi.mock('../utils/import.react_native/@react-native-community/netinfo', () => { + return { + addEventListener: mockNetInfo.addEventListener.bind(mockNetInfo), + }; +}); + +import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { getMockRepeater } from '../tests/mock/mock_repeater'; +import { getMockAsyncCache } from '../tests/mock/mock_cache'; + +import { EventWithId } from './batch_event_processor'; +import { EventDispatcher } from './eventDispatcher'; +import { formatEvents } from './v1/buildEventV1'; +import { createImpressionEvent } from '../tests/mock/create_event'; +import { ProcessableEvent } from './eventProcessor'; + +const getMockDispatcher = () => { + return { + dispatchEvent: vi.fn(), + }; +}; + +const exhaustMicrotasks = async (loop = 100) => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +} + + +describe('ReactNativeNetInfoEventProcessor', () => { + beforeEach(() => { + mockNetInfo.clear(); + }); + + it('should not retry failed events when reachable state does not change', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const cache = getMockAsyncCache<EventWithId>(); + const events: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + events.push(event); + await cache.set(id, { id, event }); + } + + const processor = new ReactNativeNetInfoEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + mockNetInfo.pushState(true); + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + + mockNetInfo.pushState(true); + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + }); + + it('should retry failed events when network becomes reachable', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const cache = getMockAsyncCache<EventWithId>(); + const events: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + events.push(event); + await cache.set(id, { id, event }); + } + + const processor = new ReactNativeNetInfoEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + mockNetInfo.pushState(false); + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + + mockNetInfo.pushState(true); + + await exhaustMicrotasks(); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents(events)); + }); + + it('should unsubscribe from netinfo listener when stopped', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const cache = getMockAsyncCache<EventWithId>(); + + const processor = new ReactNativeNetInfoEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + mockNetInfo.pushState(false); + + processor.stop(); + await processor.onTerminated(); + + expect(mockNetInfo.unsubs[0]).toHaveBeenCalled(); + }); +}); diff --git a/lib/event_processor/batch_event_processor.react_native.ts b/lib/event_processor/batch_event_processor.react_native.ts new file mode 100644 index 000000000..ac5110de4 --- /dev/null +++ b/lib/event_processor/batch_event_processor.react_native.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NetInfoState, addEventListener } from '../utils/import.react_native/@react-native-community/netinfo'; + +import { BatchEventProcessor, BatchEventProcessorConfig } from './batch_event_processor'; +import { Fn } from '../utils/type'; + +export class ReactNativeNetInfoEventProcessor extends BatchEventProcessor { + private isInternetReachable = true; + private unsubscribeNetInfo?: Fn; + + constructor(config: BatchEventProcessorConfig) { + super(config); + } + + private async connectionListener(state: NetInfoState) { + if (this.isInternetReachable && !state.isInternetReachable) { + this.isInternetReachable = false; + return; + } + + if (!this.isInternetReachable && state.isInternetReachable) { + this.isInternetReachable = true; + this.retryFailedEvents(); + } + } + + start(): void { + super.start(); + if (addEventListener) { + this.unsubscribeNetInfo = addEventListener(this.connectionListener.bind(this)); + } + } + + stop(): void { + if (this.unsubscribeNetInfo) { + this.unsubscribeNetInfo(); + } + super.stop(); + } +} diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts new file mode 100644 index 000000000..715b4452b --- /dev/null +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -0,0 +1,1223 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it, vi, beforeEach, afterEach, MockInstance } from 'vitest'; + +import { EventWithId, BatchEventProcessor } from './batch_event_processor'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { createImpressionEvent } from '../tests/mock/create_event'; +import { ProcessableEvent } from './eventProcessor'; +import { EventDispatcher } from './eventDispatcher'; +import { formatEvents } from './v1/buildEventV1'; +import { ResolvablePromise, resolvablePromise } from '../utils/promise/resolvablePromise'; +import { advanceTimersByTime } from '../../tests/testUtils'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { getMockRepeater } from '../tests/mock/mock_repeater'; +import * as retry from '../utils/executor/backoff_retry_runner'; +import { ServiceState, StartupLog } from '../service'; +import { LogLevel } from '../modules/logging'; + +const getMockDispatcher = () => { + return { + dispatchEvent: vi.fn(), + }; +}; + +const exhaustMicrotasks = async (loop = 100) => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +} + +describe('QueueingEventProcessor', async () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + describe('start', () => { + it('should log startupLogs on start', () => { + const startupLogs: StartupLog[] = [ + { + level: LogLevel.WARNING, + message: 'warn message', + params: [1, 2] + }, + { + level: LogLevel.ERROR, + message: 'error message', + params: [3, 4] + }, + ]; + + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher: getMockDispatcher(), + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + startupLogs, + }); + + processor.setLogger(logger); + processor.start(); + + + expect(logger.log).toHaveBeenCalledTimes(2); + expect(logger.log).toHaveBeenNthCalledWith(1, LogLevel.WARNING, 'warn message', 1, 2); + expect(logger.log).toHaveBeenNthCalledWith(2, LogLevel.ERROR, 'error message', 3, 4); + }); + + it('should resolve onRunning() when start() is called', async () => { + const eventDispatcher = getMockDispatcher(); + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + }); + + processor.start(); + await expect(processor.onRunning()).resolves.not.toThrow(); + }); + + it('should start dispatchRepeater and failedEventRepeater', () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + }); + + processor.start(); + expect(dispatchRepeater.start).toHaveBeenCalledOnce(); + expect(failedEventRepeater.start).toHaveBeenCalledOnce(); + }); + + it('should dispatch failed events in correct batch sizes and order', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const events: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + events.push(event); + cache.set(id, { id, event }); + } + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 2, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(3); + expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([events[0], events[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents([events[2], events[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(formatEvents([events[4]])); + }); + }); + + describe('process', () => { + it('should return a promise that rejects if processor is not running', async () => { + const eventDispatcher = getMockDispatcher(); + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + }); + + expect(processor.process(createImpressionEvent('id-1'))).rejects.toThrow(); + }); + + it('should enqueue event without dispatching immediately', async () => { + const eventDispatcher = getMockDispatcher(); + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + for(let i = 0; i < 100; i++) { + const event = createImpressionEvent(`id-${i}`); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + }); + + it('should dispatch events if queue is full and clear queue', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + let events: ProcessableEvent[] = []; + for(let i = 0; i < 100; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + let event = createImpressionEvent('id-100'); + await processor.process(event); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(formatEvents(events)); + + events = [event]; + for(let i = 101; i < 200; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + + event = createImpressionEvent('id-200'); + await processor.process(event); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(formatEvents(events)); + }); + + it('should flush queue is context of the new event is different and enqueue the new event', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 80; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + const newEvent = createImpressionEvent('id-a'); + newEvent.context.accountId = 'account-' + Math.random(); + await processor.process(newEvent); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(formatEvents(events)); + + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(formatEvents([newEvent])); + }); + + it('should store the event in the eventStore with increasing ids', async () => { + const eventDispatcher = getMockDispatcher(); + const eventStore = getMockSyncCache<EventWithId>(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + + const eventsInStore = Array.from(eventStore.getAll().values()) + .sort((a, b) => a < b ? -1 : 1).map(e => e.event); + + expect(events).toEqual(eventsInStore); + }); + }); + + it('should dispatch events when dispatchRepeater is triggered', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + let events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + await dispatchRepeater.execute(0); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(formatEvents(events)); + + events = []; + for(let i = 1; i < 15; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(formatEvents(events)); + }); + + it('should not retry failed dispatch if retryConfig is not provided', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockRejectedValue(new Error()); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + await dispatchRepeater.execute(0); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + }); + + it('should retry specified number of times using the provided backoffController', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockRejectedValue(new Error()); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(4); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + + const request = formatEvents(events); + for(let i = 0; i < 4; i++) { + expect(eventDispatcher.dispatchEvent.mock.calls[i][0]).toEqual(request); + } + }); + + it('should retry indefinitely using the provided backoffController if maxRetry is undefined', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockRejectedValue(new Error()); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + retryConfig: { + backoffProvider: () => backoffController, + }, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + await dispatchRepeater.execute(0); + + for(let i = 0; i < 200; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(201); + expect(backoffController.backoff).toHaveBeenCalledTimes(200); + + const request = formatEvents(events); + for(let i = 0; i < 201; i++) { + expect(eventDispatcher.dispatchEvent.mock.calls[i][0]).toEqual(request); + } + }); + + it('should remove the events from the eventStore after dispatch is successfull', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const dispatchResponse = resolvablePromise(); + + mockDispatch.mockResolvedValue(dispatchResponse.promise); + + const eventStore = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + await dispatchRepeater.execute(0); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + // the dispatch is not resolved yet, so all the events should still be in the store + expect(eventStore.size()).toEqual(10); + + dispatchResponse.resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + + expect(eventStore.size()).toEqual(0); + }); + + it('should remove the events from the eventStore after dispatch is successfull', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const dispatchResponse = resolvablePromise(); + + mockDispatch.mockResolvedValue(dispatchResponse.promise); + + const eventStore = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + await dispatchRepeater.execute(0); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + // the dispatch is not resolved yet, so all the events should still be in the store + expect(eventStore.size()).toEqual(10); + + dispatchResponse.resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + + expect(eventStore.size()).toEqual(0); + }); + + it('should remove the events from the eventStore after dispatch is successfull after retries', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + + mockDispatch.mockResolvedValueOnce({ statusCode: 500 }) + .mockResolvedValueOnce({ statusCode: 500 }) + .mockResolvedValueOnce({ statusCode: 200 }); + + const eventStore = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(mockDispatch).toHaveBeenCalledTimes(3); + expect(eventStore.size()).toEqual(0); + }); + + it('should log error and keep events in store if dispatch return 5xx response', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({ statusCode: 500 }); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const eventStore = getMockSyncCache<EventWithId>(); + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + eventStore, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + batchSize: 100, + logger, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(eventStore.size()).toEqual(10); + + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(4); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + expect(eventStore.size()).toEqual(10); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should log error and keep events in store if dispatch promise fails', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockRejectedValue(new Error()); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const eventStore = getMockSyncCache<EventWithId>(); + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + eventStore, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + batchSize: 100, + logger, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(eventStore.size()).toEqual(10); + + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(4); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + expect(eventStore.size()).toEqual(10); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + describe('retryFailedEvents', () => { + it('should disptach only failed events from the store and not dispatch queued events', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in queue and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + await processor.retryFailedEvents(); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents(failedEvents)); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should disptach only failed events from the store and not dispatch events that are being dispatched', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const mockResult1 = resolvablePromise(); + const mockResult2 = resolvablePromise(); + mockDispatch.mockResolvedValueOnce(mockResult1.promise).mockRejectedValueOnce(mockResult2.promise); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in dispatch and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + dispatchRepeater.execute(0); + await exhaustMicrotasks(); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([eventA, eventB])); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + await processor.retryFailedEvents(); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(2); + expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents(failedEvents)); + + mockResult2.resolve({}); + await exhaustMicrotasks(); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should disptach events in correct batch size and separate events with differnt contexts in separate batch', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 3, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 8; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + + if (i == 2 || i == 3) { + event.context.accountId = 'new-account'; + } + + failedEvents.push(event); + cache.set(id, { id, event }); + } + + await processor.retryFailedEvents(); + await exhaustMicrotasks(); + + // events 0 1 4 5 6 7 have one context, and 2 3 have different context + // batches should be [0, 1], [2, 3], [4, 5, 6], [7] + expect(mockDispatch).toHaveBeenCalledTimes(4); + expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([failedEvents[0], failedEvents[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents([failedEvents[2], failedEvents[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(formatEvents([failedEvents[4], failedEvents[5], failedEvents[6]])); + expect(mockDispatch.mock.calls[3][0]).toEqual(formatEvents([failedEvents[7]])); + }); + }); + + describe('when failedEventRepeater is fired', () => { + it('should disptach only failed events from the store and not dispatch queued events', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in queue and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + failedEventRepeater.execute(0); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents(failedEvents)); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should disptach only failed events from the store and not dispatch events that are being dispatched', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const mockResult1 = resolvablePromise(); + const mockResult2 = resolvablePromise(); + mockDispatch.mockResolvedValueOnce(mockResult1.promise).mockRejectedValueOnce(mockResult2.promise); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in dispatch and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + dispatchRepeater.execute(0); + await exhaustMicrotasks(); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([eventA, eventB])); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + failedEventRepeater.execute(0); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(2); + expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents(failedEvents)); + + mockResult2.resolve({}); + await exhaustMicrotasks(); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should disptach events in correct batch size and separate events with differnt contexts in separate batch', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 3, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 8; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + + if (i == 2 || i == 3) { + event.context.accountId = 'new-account'; + } + + failedEvents.push(event); + cache.set(id, { id, event }); + } + + failedEventRepeater.execute(0); + await exhaustMicrotasks(); + + // events 0 1 4 5 6 7 have one context, and 2 3 have different context + // batches should be [0, 1], [2, 3], [4, 5, 6], [7] + expect(mockDispatch).toHaveBeenCalledTimes(4); + expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([failedEvents[0], failedEvents[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents([failedEvents[2], failedEvents[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(formatEvents([failedEvents[4], failedEvents[5], failedEvents[6]])); + expect(mockDispatch.mock.calls[3][0]).toEqual(formatEvents([failedEvents[7]])); + }); + }); + + it('should emit dispatch event when dispatching events', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + const event = createImpressionEvent('id-1'); + const event2 = createImpressionEvent('id-2'); + + const dispatchListener = vi.fn(); + processor.onDispatch(dispatchListener); + + processor.start(); + await processor.onRunning(); + + await processor.process(event); + await processor.process(event2); + + await dispatchRepeater.execute(0); + + expect(dispatchListener).toHaveBeenCalledTimes(1); + expect(dispatchListener.mock.calls[0][0]).toEqual(formatEvents([event, event2])); + }); + + it('should remove event handler when function returned from onDispatch is called', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + const dispatchListener = vi.fn(); + + const unsub = processor.onDispatch(dispatchListener); + + processor.start(); + await processor.onRunning(); + + const event = createImpressionEvent('id-1'); + const event2 = createImpressionEvent('id-2'); + + await processor.process(event); + await processor.process(event2); + + await dispatchRepeater.execute(0); + + expect(dispatchListener).toHaveBeenCalledTimes(1); + expect(dispatchListener.mock.calls[0][0]).toEqual(formatEvents([event, event2])); + + unsub(); + + const event3 = createImpressionEvent('id-3'); + const event4 = createImpressionEvent('id-4'); + + await dispatchRepeater.execute(0); + expect(dispatchListener).toHaveBeenCalledTimes(1); + }); + + describe('stop', () => { + it('should reject onRunning if stop is called before the processor is started', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.stop(); + + await expect(processor.onRunning()).rejects.toThrow(); + }); + + it('should stop dispatchRepeater and failedEventRepeater', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + processor.stop(); + expect(dispatchRepeater.stop).toHaveBeenCalledOnce(); + expect(failedEventRepeater.stop).toHaveBeenCalledOnce(); + }); + + it('should disptach the events in queue using the closing dispatcher if available', async () => { + const eventDispatcher = getMockDispatcher(); + const closingEventDispatcher = getMockDispatcher(); + closingEventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + closingEventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.stop(); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents(events)); + }); + + it('should cancel retry of active dispatches', async () => { + const runWithRetrySpy = vi.spyOn(retry, 'runWithRetry'); + const cancel1 = vi.fn(); + const cancel2 = vi.fn(); + runWithRetrySpy.mockReturnValueOnce({ + cancelRetry: cancel1, + result: resolvablePromise().promise, + }).mockReturnValueOnce({ + cancelRetry: cancel2, + result: resolvablePromise().promise, + }); + + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + } + }); + + processor.start(); + await processor.onRunning(); + + await processor.process(createImpressionEvent('id-1')); + await dispatchRepeater.execute(0); + + expect(runWithRetrySpy).toHaveBeenCalledTimes(1); + + await processor.process(createImpressionEvent('id-2')); + await dispatchRepeater.execute(0); + + expect(runWithRetrySpy).toHaveBeenCalledTimes(2); + + processor.stop(); + + expect(cancel1).toHaveBeenCalledOnce(); + expect(cancel2).toHaveBeenCalledOnce(); + + runWithRetrySpy.mockReset(); + }); + + it('should resolve onTerminated when all active dispatch requests settles' , async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRes1 = resolvablePromise<void>(); + const dispatchRes2 = resolvablePromise<void>(); + eventDispatcher.dispatchEvent.mockReturnValueOnce(dispatchRes1.promise) + .mockReturnValueOnce(dispatchRes2.promise); + + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start() + await processor.onRunning(); + + await processor.process(createImpressionEvent('id-1')); + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + + await processor.process(createImpressionEvent('id-2')); + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + + const onStop = vi.fn(); + processor.onTerminated().then(onStop); + + processor.stop(); + + await exhaustMicrotasks(); + expect(onStop).not.toHaveBeenCalled(); + expect(processor.getState()).toEqual(ServiceState.Stopping); + + dispatchRes1.resolve(); + dispatchRes2.reject(new Error()); + + await expect(processor.onTerminated()).resolves.not.toThrow(); + }); + }); +}); diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts new file mode 100644 index 000000000..7cad445cd --- /dev/null +++ b/lib/event_processor/batch_event_processor.ts @@ -0,0 +1,271 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EventProcessor, ProcessableEvent } from "./eventProcessor"; +import { Cache } from "../utils/cache/cache"; +import { EventDispatcher, EventDispatcherResponse, EventV1Request } from "./eventDispatcher"; +import { formatEvents } from "../core/event_builder/build_event_v1"; +import { BackoffController, ExponentialBackoff, IntervalRepeater, Repeater } from "../utils/repeater/repeater"; +import { LoggerFacade } from "../modules/logging"; +import { BaseService, ServiceState, StartupLog } from "../service"; +import { Consumer, Fn, Producer } from "../utils/type"; +import { RunResult, runWithRetry } from "../utils/executor/backoff_retry_runner"; +import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; +import { EventEmitter } from "../utils/event_emitter/event_emitter"; +import { IdGenerator } from "../utils/id_generator"; +import { areEventContextsEqual } from "./events"; + +export type EventWithId = { + id: string; + event: ProcessableEvent; +}; + +export type RetryConfig = { + maxRetries?: number; + backoffProvider: Producer<BackoffController>; +} + +export type BatchEventProcessorConfig = { + dispatchRepeater: Repeater, + failedEventRepeater?: Repeater, + batchSize: number, + eventStore?: Cache<EventWithId>, + eventDispatcher: EventDispatcher, + closingEventDispatcher?: EventDispatcher, + logger?: LoggerFacade, + retryConfig?: RetryConfig; + startupLogs?: StartupLog[]; +}; + +type EventBatch = { + request: EventV1Request, + ids: string[], +} + +export class BatchEventProcessor extends BaseService implements EventProcessor { + private eventDispatcher: EventDispatcher; + private closingEventDispatcher?: EventDispatcher; + private eventQueue: EventWithId[] = []; + private batchSize: number; + private eventStore?: Cache<EventWithId>; + private dispatchRepeater: Repeater; + private failedEventRepeater?: Repeater; + private idGenerator: IdGenerator = new IdGenerator(); + private runningTask: Map<string, RunResult<EventDispatcherResponse>> = new Map(); + private dispatchingEventIds: Set<string> = new Set(); + private eventEmitter: EventEmitter<{ dispatch: EventV1Request }> = new EventEmitter(); + private retryConfig?: RetryConfig; + + constructor(config: BatchEventProcessorConfig) { + super(config.startupLogs); + this.eventDispatcher = config.eventDispatcher; + this.closingEventDispatcher = config.closingEventDispatcher; + this.batchSize = config.batchSize; + this.eventStore = config.eventStore; + this.logger = config.logger; + this.retryConfig = config.retryConfig; + + this.dispatchRepeater = config.dispatchRepeater; + this.dispatchRepeater.setTask(() => this.flush()); + + this.failedEventRepeater = config.failedEventRepeater; + this.failedEventRepeater?.setTask(() => this.retryFailedEvents()); + } + + onDispatch(handler: Consumer<EventV1Request>): Fn { + return this.eventEmitter.on('dispatch', handler); + } + + public async retryFailedEvents(): Promise<void> { + if (!this.eventStore) { + return; + } + + const keys = (await this.eventStore.getKeys()).filter( + (k) => !this.dispatchingEventIds.has(k) && !this.eventQueue.find((e) => e.id === k) + ); + + const events = await this.eventStore.getBatched(keys); + const failedEvents: EventWithId[] = []; + events.forEach((e) => { + if(e) { + failedEvents.push(e); + } + }); + + if (failedEvents.length == 0) { + return; + } + + failedEvents.sort((a, b) => a.id < b.id ? -1 : 1); + + const batches: EventBatch[] = []; + let currentBatch: EventWithId[] = []; + + failedEvents.forEach((event) => { + if (currentBatch.length === this.batchSize || + (currentBatch.length > 0 && !areEventContextsEqual(currentBatch[0].event, event.event))) { + batches.push({ + request: formatEvents(currentBatch.map((e) => e.event)), + ids: currentBatch.map((e) => e.id), + }); + currentBatch = []; + } + currentBatch.push(event); + }); + + if (currentBatch.length > 0) { + batches.push({ + request: formatEvents(currentBatch.map((e) => e.event)), + ids: currentBatch.map((e) => e.id), + }); + } + + batches.forEach((batch) => { + this.dispatchBatch(batch, false); + }); + } + + private createNewBatch(): EventBatch | undefined { + if (this.eventQueue.length == 0) { + return + } + + const events: ProcessableEvent[] = []; + const ids: string[] = []; + + this.eventQueue.forEach((event) => { + events.push(event.event); + ids.push(event.id); + }); + + this.eventQueue = []; + return { request: formatEvents(events), ids }; + } + + private async executeDispatch(request: EventV1Request, closing = false): Promise<EventDispatcherResponse> { + const dispatcher = closing && this.closingEventDispatcher ? this.closingEventDispatcher : this.eventDispatcher; + return dispatcher.dispatchEvent(request).then((res) => { + if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { + return Promise.reject(new Error(`Failed to dispatch events: ${res.statusCode}`)); + } + return Promise.resolve(res); + }); + } + + private dispatchBatch(batch: EventBatch, closing: boolean): void { + const { request, ids } = batch; + + ids.forEach((id) => { + this.dispatchingEventIds.add(id); + }); + + const runResult: RunResult<EventDispatcherResponse> = this.retryConfig + ? runWithRetry( + () => this.executeDispatch(request, closing), this.retryConfig.backoffProvider(), this.retryConfig.maxRetries + ) : { + result: this.executeDispatch(request, closing), + cancelRetry: () => {}, + }; + + this.eventEmitter.emit('dispatch', request); + + const taskId = this.idGenerator.getId(); + this.runningTask.set(taskId, runResult); + + runResult.result.then((res) => { + ids.forEach((id) => { + this.dispatchingEventIds.delete(id); + this.eventStore?.remove(id); + }); + return Promise.resolve(); + }).catch((err) => { + // if the dispatch fails, the events will still be + // in the store for future processing + this.logger?.error('Failed to dispatch events', err); + }).finally(() => { + this.runningTask.delete(taskId); + ids.forEach((id) => this.dispatchingEventIds.delete(id)); + }); + } + + private async flush(closing = false): Promise<void> { + const batch = this.createNewBatch(); + if (!batch) { + return; + } + + this.dispatchBatch(batch, closing); + } + + async process(event: ProcessableEvent): Promise<void> { + if (!this.isRunning()) { + return Promise.reject('Event processor is not running'); + } + + if (this.eventQueue.length == this.batchSize) { + this.flush(); + } + + const eventWithId = { + id: this.idGenerator.getId(), + event: event, + }; + + await this.eventStore?.set(eventWithId.id, eventWithId); + + if (this.eventQueue.length > 0 && !areEventContextsEqual(this.eventQueue[0].event, event)) { + this.flush(); + } + this.eventQueue.push(eventWithId); + } + + start(): void { + if (!this.isNew()) { + return; + } + super.start(); + this.state = ServiceState.Running; + this.dispatchRepeater.start(); + this.failedEventRepeater?.start(); + + this.retryFailedEvents(); + this.startPromise.resolve(); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew()) { + // TOOD: replace message with imported constants + this.startPromise.reject(new Error('Event processor stopped before it could be started')); + } + + this.state = ServiceState.Stopping; + this.dispatchRepeater.stop(); + this.failedEventRepeater?.stop(); + + this.flush(true); + this.runningTask.forEach((task) => task.cancelRetry()); + + Promise.allSettled(Array.from(this.runningTask.values()).map((task) => task.result)).then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + }); + } +} diff --git a/lib/event_processor/default_dispatcher.browser.ts b/lib/event_processor/default_dispatcher.browser.ts index 12cdf5a3e..d4601700c 100644 --- a/lib/event_processor/default_dispatcher.browser.ts +++ b/lib/event_processor/default_dispatcher.browser.ts @@ -15,7 +15,7 @@ */ import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; -import { EventDispatcher } from '../event_processor'; +import { EventDispatcher } from '../event_processor/eventDispatcher'; import { DefaultEventDispatcher } from './default_dispatcher'; const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new BrowserRequestHandler()); diff --git a/lib/event_processor/default_dispatcher.node.ts b/lib/event_processor/default_dispatcher.node.ts index 8d2cd852c..75e00aff3 100644 --- a/lib/event_processor/default_dispatcher.node.ts +++ b/lib/event_processor/default_dispatcher.node.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventDispatcher } from '../event_processor'; +import { EventDispatcher } from '../event_processor/eventDispatcher'; import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; import { DefaultEventDispatcher } from './default_dispatcher'; diff --git a/lib/event_processor/default_dispatcher.ts b/lib/event_processor/default_dispatcher.ts index 2097cb82c..ce8dd5b59 100644 --- a/lib/event_processor/default_dispatcher.ts +++ b/lib/event_processor/default_dispatcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { RequestHandler } from '../utils/http_request_handler/http'; -import { EventDispatcher, EventDispatcherResponse, EventV1Request } from '../event_processor'; +import { EventDispatcher, EventDispatcherResponse, EventV1Request } from '../event_processor/eventDispatcher'; export class DefaultEventDispatcher implements EventDispatcher { private requestHandler: RequestHandler; diff --git a/lib/event_processor/eventProcessor.ts b/lib/event_processor/eventProcessor.ts index fa2cab200..656beab90 100644 --- a/lib/event_processor/eventProcessor.ts +++ b/lib/event_processor/eventProcessor.ts @@ -13,77 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' import { ConversionEvent, ImpressionEvent } from './events' import { EventV1Request } from './eventDispatcher' -import { EventQueue, DefaultEventQueue, SingleEventQueue, EventQueueSink } from './eventQueue' import { getLogger } from '../modules/logging' -import { NOTIFICATION_TYPES } from '../utils/enums' -import { NotificationSender } from '../core/notification_center' +import { Service } from '../service' +import { Consumer, Fn } from '../utils/type'; export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s export const DEFAULT_BATCH_SIZE = 10 -const logger = getLogger('EventProcessor') - export type ProcessableEvent = ConversionEvent | ImpressionEvent -export type EventDispatchResult = { result: boolean; event: ProcessableEvent } - -export interface EventProcessor extends Managed { - process(event: ProcessableEvent): void -} - -export function validateAndGetFlushInterval(flushInterval: number): number { - if (flushInterval <= 0) { - logger.warn( - `Invalid flushInterval ${flushInterval}, defaulting to ${DEFAULT_FLUSH_INTERVAL}`, - ) - flushInterval = DEFAULT_FLUSH_INTERVAL - } - return flushInterval -} - -export function validateAndGetBatchSize(batchSize: number): number { - batchSize = Math.floor(batchSize) - if (batchSize < 1) { - logger.warn( - `Invalid batchSize ${batchSize}, defaulting to ${DEFAULT_BATCH_SIZE}`, - ) - batchSize = DEFAULT_BATCH_SIZE - } - batchSize = Math.max(1, batchSize) - return batchSize -} - -export function getQueue( - batchSize: number, - flushInterval: number, - batchComparator: (eventA: ProcessableEvent, eventB: ProcessableEvent) => boolean, - sink: EventQueueSink<ProcessableEvent>, - closingSink?: EventQueueSink<ProcessableEvent> -): EventQueue<ProcessableEvent> { - let queue: EventQueue<ProcessableEvent> - if (batchSize > 1) { - queue = new DefaultEventQueue<ProcessableEvent>({ - flushInterval, - maxQueueSize: batchSize, - sink, - closingSink, - batchComparator, - }) - } else { - queue = new SingleEventQueue({ sink }) - } - return queue -} - -export function sendEventNotification(notificationSender: NotificationSender | undefined, event: EventV1Request): void { - if (notificationSender) { - notificationSender.sendNotifications( - NOTIFICATION_TYPES.LOG_EVENT, - event, - ) - } +export interface EventProcessor extends Service { + process(event: ProcessableEvent): Promise<unknown>; + onDispatch(handler: Consumer<EventV1Request>): Fn; } diff --git a/lib/event_processor/eventQueue.ts b/lib/event_processor/eventQueue.ts deleted file mode 100644 index 3b8a71966..000000000 --- a/lib/event_processor/eventQueue.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '../modules/logging'; -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed'; - -const logger = getLogger('EventProcessor'); - -export type EventQueueSink<K> = (buffer: K[]) => Promise<any>; - -export interface EventQueue<K> extends Managed { - enqueue(event: K): void; -} - -export interface EventQueueFactory<K> { - createEventQueue(config: { sink: EventQueueSink<K>, flushInterval: number, maxQueueSize: number }): EventQueue<K>; -} - -class Timer { - private timeout: number; - private callback: () => void; - private timeoutId?: number; - - constructor({ timeout, callback }: { timeout: number; callback: () => void }) { - this.timeout = Math.max(timeout, 0); - this.callback = callback; - } - - start(): void { - this.timeoutId = setTimeout(this.callback, this.timeout) as any; - } - - refresh(): void { - this.stop(); - this.start(); - } - - stop(): void { - if (this.timeoutId) { - clearTimeout(this.timeoutId as any); - } - } -} - -export class SingleEventQueue<K> implements EventQueue<K> { - private sink: EventQueueSink<K>; - - constructor({ sink }: { sink: EventQueueSink<K> }) { - this.sink = sink; - } - - start(): Promise<any> { - // no-op - return Promise.resolve(); - } - - stop(): Promise<any> { - // no-op - return Promise.resolve(); - } - - enqueue(event: K): void { - this.sink([event]); - } -} - -export class DefaultEventQueue<K> implements EventQueue<K> { - // expose for testing - public timer: Timer; - private buffer: K[]; - private maxQueueSize: number; - private sink: EventQueueSink<K>; - private closingSink?: EventQueueSink<K>; - // batchComparator is called to determine whether two events can be included - // together in the same batch - private batchComparator: (eventA: K, eventB: K) => boolean; - private started: boolean; - - constructor({ - flushInterval, - maxQueueSize, - sink, - closingSink, - batchComparator, - }: { - flushInterval: number; - maxQueueSize: number; - sink: EventQueueSink<K>; - closingSink?: EventQueueSink<K>; - batchComparator: (eventA: K, eventB: K) => boolean; - }) { - this.buffer = []; - this.maxQueueSize = Math.max(maxQueueSize, 1); - this.sink = sink; - this.closingSink = closingSink; - this.batchComparator = batchComparator; - this.timer = new Timer({ - callback: this.flush.bind(this), - timeout: flushInterval, - }); - this.started = false; - } - - start(): Promise<any> { - this.started = true; - // dont start the timer until the first event is enqueued - - return Promise.resolve(); - } - - stop(): Promise<any> { - this.started = false; - const result = this.closingSink ? this.closingSink(this.buffer) : this.sink(this.buffer); - this.buffer = []; - this.timer.stop(); - return result; - } - - enqueue(event: K): void { - if (!this.started) { - logger.warn('Queue is stopped, not accepting event'); - return; - } - - // If new event cannot be included into the current batch, flush so it can - // be in its own new batch. - const bufferedEvent: K | undefined = this.buffer[0]; - if (bufferedEvent && !this.batchComparator(bufferedEvent, event)) { - this.flush(); - } - - // start the timer when the first event is put in - if (this.buffer.length === 0) { - this.timer.refresh(); - } - this.buffer.push(event); - - if (this.buffer.length >= this.maxQueueSize) { - this.flush(); - } - } - - flush(): void { - this.sink(this.buffer); - this.buffer = []; - this.timer.stop(); - } -} diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts index b63471a29..5bd615ebe 100644 --- a/lib/event_processor/event_processor_factory.browser.spec.ts +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -20,13 +20,38 @@ vi.mock('./default_dispatcher.browser', () => { }); vi.mock('./forwarding_event_processor', () => { - const getForwardingEventProcessor = vi.fn().mockReturnValue({}); + const getForwardingEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); return { getForwardingEventProcessor }; }); -import { createForwardingEventProcessor } from './event_processor_factory.browser'; +vi.mock('./event_processor_factory', async (importOriginal) => { + const getBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const original: any = await importOriginal(); + return { ...original, getBatchEventProcessor }; +}); + +vi.mock('../utils/cache/local_storage_cache.browser', () => { + return { LocalStorageCache: vi.fn() }; +}); + +vi.mock('../utils/cache/cache', () => { + return { SyncPrefixCache: vi.fn() }; +}); + + +import defaultEventDispatcher from './default_dispatcher.browser'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { SyncPrefixCache } from '../utils/cache/cache'; +import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.browser'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import sendBeaconEventDispatcher from '../plugins/event_dispatcher/send_beacon_dispatcher'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import browserDefaultEventDispatcher from './default_dispatcher.browser'; +import { getBatchEventProcessor } from './event_processor_factory'; describe('createForwardingEventProcessor', () => { const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); @@ -53,3 +78,104 @@ describe('createForwardingEventProcessor', () => { expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, browserDefaultEventDispatcher); }); }); + +describe('createBatchEventProcessor', () => { + const mockGetBatchEventProcessor = vi.mocked(getBatchEventProcessor); + const MockLocalStorageCache = vi.mocked(LocalStorageCache); + const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); + + beforeEach(() => { + mockGetBatchEventProcessor.mockClear(); + MockLocalStorageCache.mockClear(); + MockSyncPrefixCache.mockClear(); + }); + + it('uses LocalStorageCache and SyncPrefixCache to create eventStore', () => { + const processor = createBatchEventProcessor({}); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetBatchEventProcessor.mock.calls[0][0].eventStore; + expect(Object.is(eventStore, MockSyncPrefixCache.mock.results[0].value)).toBe(true); + + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; + expect(Object.is(cache, MockLocalStorageCache.mock.results[0].value)).toBe(true); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be identity functions + expect(transformGet('value')).toBe('value'); + expect(transformSet('value')).toBe('value'); + }); + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('uses the default browser event dispatcher if none is provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ closingEventDispatcher }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + }); + + it('does not use any closingEventDispatcher if eventDispatcher is provided but closingEventDispatcher is not', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the default sendBeacon event dispatcher if neither eventDispatcher nor closingEventDispatcher is provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(sendBeaconEventDispatcher); + }); + + it('uses the provided flushInterval', () => { + const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); + expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + }); + + it('uses the provided batchSize', () => { + const processor1 = createBatchEventProcessor({ batchSize: 20 }); + expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + }); + + it('uses maxRetries value of 5', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); + }); + + it('uses the default failedEventRetryInterval', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + }); +}); diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index ea4d2d2b1..476186030 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -17,10 +17,42 @@ import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './eventDispatcher'; import { EventProcessor } from './eventProcessor'; +import { EventWithId } from './batch_event_processor'; +import { getBatchEventProcessor, BatchEventProcessorOptions } from './event_processor_factory'; import defaultEventDispatcher from './default_dispatcher.browser'; +import sendBeaconEventDispatcher from '../plugins/event_dispatcher/send_beacon_dispatcher'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { SyncPrefixCache } from '../utils/cache/cache'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, ): EventProcessor => { return getForwardingEventProcessor(eventDispatcher); }; + +const identity = <T>(v: T): T => v; + +export const createBatchEventProcessor = ( + options: BatchEventProcessorOptions +): EventProcessor => { + const localStorageCache = new LocalStorageCache<EventWithId>(); + const eventStore = new SyncPrefixCache<EventWithId, EventWithId>( + localStorageCache, EVENT_STORE_PREFIX, + identity, + identity, + ); + + return getBatchEventProcessor({ + eventDispatcher: options.eventDispatcher || defaultEventDispatcher, + closingEventDispatcher: options.closingEventDispatcher || + (options.eventDispatcher ? undefined : sendBeaconEventDispatcher), + flushInterval: options.flushInterval, + batchSize: options.batchSize, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + eventStore, + }); +}; diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts index 36d4ea1fa..a511e2e06 100644 --- a/lib/event_processor/event_processor_factory.node.spec.ts +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -24,9 +24,29 @@ vi.mock('./forwarding_event_processor', () => { return { getForwardingEventProcessor }; }); -import { createForwardingEventProcessor } from './event_processor_factory.node'; +vi.mock('./event_processor_factory', async (importOriginal) => { + const getBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const original: any = await importOriginal(); + return { ...original, getBatchEventProcessor }; +}); + +vi.mock('../utils/cache/async_storage_cache.react_native', () => { + return { AsyncStorageCache: vi.fn() }; +}); + +vi.mock('../utils/cache/cache', () => { + return { SyncPrefixCache: vi.fn(), AsyncPrefixCache: vi.fn() }; +}); + +import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor_factory.node'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import nodeDefaultEventDispatcher from './default_dispatcher.node'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { getBatchEventProcessor } from './event_processor_factory'; +import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; describe('createForwardingEventProcessor', () => { const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); @@ -53,3 +73,132 @@ describe('createForwardingEventProcessor', () => { expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, nodeDefaultEventDispatcher); }); }); + +describe('createBatchEventProcessor', () => { + const mockGetBatchEventProcessor = vi.mocked(getBatchEventProcessor); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); + const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); + const MockAsyncPrefixCache = vi.mocked(AsyncPrefixCache); + + beforeEach(() => { + mockGetBatchEventProcessor.mockClear(); + MockAsyncStorageCache.mockClear(); + MockSyncPrefixCache.mockClear(); + MockAsyncPrefixCache.mockClear(); + }); + + it('uses no default event store if no eventStore is provided', () => { + const processor = createBatchEventProcessor({}); + + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetBatchEventProcessor.mock.calls[0][0].eventStore; + expect(eventStore).toBe(undefined); + }); + + it('wraps the provided eventStore in a SyncPrefixCache if a SyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'sync', + } as SyncCache<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + it('wraps the provided eventStore in a AsyncPrefixCache if a AsyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'async', + } as AsyncCache<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('uses the default node event dispatcher if none is provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(nodeDefaultEventDispatcher); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ closingEventDispatcher }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the provided flushInterval', () => { + const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); + expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + }); + + it('uses the provided batchSize', () => { + const processor1 = createBatchEventProcessor({ batchSize: 20 }); + expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + }); + + it('uses maxRetries value of 10', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(10); + }); + + it('uses no failed event retry if an eventStore is not provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(undefined); + }); + + it('uses the default failedEventRetryInterval if an eventStore is provided', () => { + const processor = createBatchEventProcessor({ eventStore: {} as any }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + }); +}); diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index ae793ce4f..7bfd43c6a 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -17,9 +17,29 @@ import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './eventDispatcher'; import { EventProcessor } from './eventProcessor'; import defaultEventDispatcher from './default_dispatcher.node'; +import { BatchEventProcessorOptions, FAILED_EVENT_RETRY_INTERVAL, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, ): EventProcessor => { return getForwardingEventProcessor(eventDispatcher); }; + + +export const createBatchEventProcessor = ( + options: BatchEventProcessorOptions +): EventProcessor => { + const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : undefined; + + return getBatchEventProcessor({ + eventDispatcher: options.eventDispatcher || defaultEventDispatcher, + closingEventDispatcher: options.closingEventDispatcher, + flushInterval: options.flushInterval, + batchSize: options.batchSize, + retryOptions: { + maxRetries: 10, + }, + failedEventRetryInterval: eventStore ? FAILED_EVENT_RETRY_INTERVAL : undefined, + eventStore, + }); +}; diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 6de989534..93e7a05ad 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -25,17 +25,64 @@ vi.mock('./forwarding_event_processor', () => { return { getForwardingEventProcessor }; }); -import { createForwardingEventProcessor } from './event_processor_factory.react_native'; +vi.mock('./event_processor_factory', async (importOriginal) => { + const getBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const original: any = await importOriginal(); + return { ...original, getBatchEventProcessor }; +}); + +vi.mock('../utils/cache/async_storage_cache.react_native', () => { + return { AsyncStorageCache: vi.fn() }; +}); + +vi.mock('../utils/cache/cache', () => { + return { SyncPrefixCache: vi.fn(), AsyncPrefixCache: vi.fn() }; +}); + +vi.mock('@react-native-community/netinfo', () => { + return { NetInfoState: {}, addEventListener: vi.fn() }; +}); + +let isNetInfoAvailable = false; + +await vi.hoisted(async () => { + await mockRequireNetInfo(); +}); + +async function mockRequireNetInfo() { + const {Module} = await import('module'); + const M: any = Module; + + M._load_original = M._load; + M._load = (uri: string, parent: string) => { + if (uri === '@react-native-community/netinfo') { + if (isNetInfoAvailable) return {}; + throw new Error('Module not found: @react-native-community/netinfo'); + } + return M._load_original(uri, parent); + }; +} + +import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; import { getForwardingEventProcessor } from './forwarding_event_processor'; -import browserDefaultEventDispatcher from './default_dispatcher.browser'; +import defaultEventDispatcher from './default_dispatcher.browser'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { getBatchEventProcessor } from './event_processor_factory'; +import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; +import { BatchEventProcessor } from './batch_event_processor'; describe('createForwardingEventProcessor', () => { const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); beforeEach(() => { mockGetForwardingEventProcessor.mockClear(); + isNetInfoAvailable = false; }); - + it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { const eventDispatcher = { dispatchEvent: vi.fn(), @@ -51,6 +98,152 @@ describe('createForwardingEventProcessor', () => { const processor = createForwardingEventProcessor(); expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, browserDefaultEventDispatcher); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, defaultEventDispatcher); + }); +}); + +describe('createBatchEventProcessor', () => { + const mockGetBatchEventProcessor = vi.mocked(getBatchEventProcessor); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); + const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); + const MockAsyncPrefixCache = vi.mocked(AsyncPrefixCache); + + beforeEach(() => { + isNetInfoAvailable = false; + mockGetBatchEventProcessor.mockClear(); + MockAsyncStorageCache.mockClear(); + MockSyncPrefixCache.mockClear(); + MockAsyncPrefixCache.mockClear(); + }); + + it('returns an instance of ReacNativeNetInfoEventProcessor if netinfo can be required', async () => { + isNetInfoAvailable = true; + const processor = createBatchEventProcessor({}); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][1]).toBe(ReactNativeNetInfoEventProcessor); + }); + + it('returns an instance of BatchEventProcessor if netinfo cannot be required', async () => { + isNetInfoAvailable = false; + const processor = createBatchEventProcessor({}); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][1]).toBe(BatchEventProcessor); + }); + + it('uses AsyncStorageCache and AsyncPrefixCache to create eventStore if no eventStore is provided', () => { + const processor = createBatchEventProcessor({}); + + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetBatchEventProcessor.mock.calls[0][0].eventStore; + expect(Object.is(eventStore, MockAsyncPrefixCache.mock.results[0].value)).toBe(true); + + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; + expect(Object.is(cache, MockAsyncStorageCache.mock.results[0].value)).toBe(true); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be identity functions + expect(transformGet('value')).toBe('value'); + expect(transformSet('value')).toBe('value'); + }); + + it('wraps the provided eventStore in a SyncPrefixCache if a SyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'sync', + } as SyncCache<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + it('wraps the provided eventStore in a AsyncPrefixCache if a AsyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'async', + } as AsyncCache<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('uses the default browser event dispatcher if none is provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ closingEventDispatcher }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the provided flushInterval', () => { + const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); + expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + }); + + it('uses the provided batchSize', () => { + const processor1 = createBatchEventProcessor({ batchSize: 20 }); + expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + }); + + it('uses maxRetries value of 5', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); + }); + + it('uses the default failedEventRetryInterval', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); }); }); diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index 3763a15c1..84c11e375 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -17,9 +17,49 @@ import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './eventDispatcher'; import { EventProcessor } from './eventProcessor'; import defaultEventDispatcher from './default_dispatcher.browser'; +import { BatchEventProcessorOptions, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { AsyncPrefixCache } from '../utils/cache/cache'; +import { BatchEventProcessor, EventWithId } from './batch_event_processor'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; +import { isAvailable as isNetInfoAvailable } from '../utils/import.react_native/@react-native-community/netinfo'; export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, ): EventProcessor => { return getForwardingEventProcessor(eventDispatcher); }; + +const identity = <T>(v: T): T => v; + +const getDefaultEventStore = () => { + const asyncStorageCache = new AsyncStorageCache<EventWithId>(); + + const eventStore = new AsyncPrefixCache<EventWithId, EventWithId>( + asyncStorageCache, + EVENT_STORE_PREFIX, + identity, + identity, + ); + + return eventStore; +} + +export const createBatchEventProcessor = ( + options: BatchEventProcessorOptions +): EventProcessor => { + const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : getDefaultEventStore(); + + return getBatchEventProcessor({ + eventDispatcher: options.eventDispatcher || defaultEventDispatcher, + closingEventDispatcher: options.closingEventDispatcher, + flushInterval: options.flushInterval, + batchSize: options.batchSize, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + eventStore, + }, isNetInfoAvailable() ? ReactNativeNetInfoEventProcessor : BatchEventProcessor); +}; diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts new file mode 100644 index 000000000..2f3d45408 --- /dev/null +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -0,0 +1,317 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi, MockInstance } from 'vitest'; +import { DEFAULT_EVENT_BATCH_SIZE, DEFAULT_EVENT_FLUSH_INTERVAL, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, getBatchEventProcessor } from './event_processor_factory'; +import { BatchEventProcessor, BatchEventProcessorConfig, EventWithId } from './batch_event_processor'; +import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { LogLevel } from '../modules/logging'; + +vi.mock('./batch_event_processor'); +vi.mock('../utils/repeater/repeater'); + +const getMockEventDispatcher = () => { + return { + dispatchEvent: vi.fn(), + } +}; + +describe('getBatchEventProcessor', () => { + const MockBatchEventProcessor = vi.mocked(BatchEventProcessor); + const MockExponentialBackoff = vi.mocked(ExponentialBackoff); + const MockIntervalRepeater = vi.mocked(IntervalRepeater); + + beforeEach(() => { + MockBatchEventProcessor.mockReset(); + MockExponentialBackoff.mockReset(); + MockIntervalRepeater.mockReset(); + }); + + it('returns an instane of BatchEventProcessor if no subclass constructor is provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + }; + + const processor = getBatchEventProcessor(options); + + expect(processor instanceof BatchEventProcessor).toBe(true); + }); + + it('returns an instane of the provided subclass constructor', () => { + class CustomEventProcessor extends BatchEventProcessor { + constructor(opts: BatchEventProcessorConfig) { + super(opts); + } + } + + const options = { + eventDispatcher: getMockEventDispatcher(), + }; + + const processor = getBatchEventProcessor(options, CustomEventProcessor); + + expect(processor instanceof CustomEventProcessor).toBe(true); + }); + + it('does not use retry if retryOptions is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + }; + + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig).toBe(undefined); + }); + + it('uses retry when retryOptions is provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + retryOptions: {}, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const usedRetryConfig = MockBatchEventProcessor.mock.calls[0][0].retryConfig; + expect(usedRetryConfig).not.toBe(undefined); + expect(usedRetryConfig?.backoffProvider).not.toBe(undefined); + }); + + it('uses the correct maxRetries value when retryOptions is provided', () => { + const options1 = { + eventDispatcher: getMockEventDispatcher(), + retryOptions: { + maxRetries: 10, + }, + }; + + const processor1 = getBatchEventProcessor(options1); + expect(Object.is(processor1, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig?.maxRetries).toBe(10); + + const options2 = { + eventDispatcher: getMockEventDispatcher(), + retryOptions: {}, + }; + + const processor2 = getBatchEventProcessor(options2); + expect(Object.is(processor2, MockBatchEventProcessor.mock.instances[1])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig).not.toBe(undefined); + expect(MockBatchEventProcessor.mock.calls[1][0].retryConfig?.maxRetries).toBe(undefined); + }); + + it('uses exponential backoff with default parameters when retryOptions is provided without backoff values', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + retryOptions: {}, + }; + + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + + const backoffProvider = MockBatchEventProcessor.mock.calls[0][0].retryConfig?.backoffProvider; + expect(backoffProvider).not.toBe(undefined); + const backoff = backoffProvider?.(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff).toHaveBeenNthCalledWith(1, DEFAULT_MIN_BACKOFF, DEFAULT_MAX_BACKOFF, 500); + }); + + it('uses exponential backoff with provided backoff values in retryOptions', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + retryOptions: { minBackoff: 1000, maxBackoff: 2000 }, + }; + + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const backoffProvider = MockBatchEventProcessor.mock.calls[0][0].retryConfig?.backoffProvider; + + expect(backoffProvider).not.toBe(undefined); + const backoff = backoffProvider?.(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff).toHaveBeenNthCalledWith(1, 1000, 2000, 500); + }); + + it('uses a IntervalRepeater with default flush interval and adds a startup log if flushInterval is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, DEFAULT_EVENT_FLUSH_INTERVAL); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.WARNING, + message: 'Invalid flushInterval %s, defaulting to %s', + params: [undefined, DEFAULT_EVENT_FLUSH_INTERVAL], + }])); + }); + + it('uses default flush interval and adds a startup log if flushInterval is less than 1', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + flushInterval: -1, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, DEFAULT_EVENT_FLUSH_INTERVAL); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.WARNING, + message: 'Invalid flushInterval %s, defaulting to %s', + params: [-1, DEFAULT_EVENT_FLUSH_INTERVAL], + }])); + }); + + it('uses a IntervalRepeater with provided flushInterval and adds no startup log if provided flushInterval is valid', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + flushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, 12345); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs?.find((log) => log.message === 'Invalid flushInterval %s, defaulting to %s')).toBe(undefined); + }); + + + it('uses a IntervalRepeater with default flush interval and adds a startup log if flushInterval is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(DEFAULT_EVENT_BATCH_SIZE); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.WARNING, + message: 'Invalid batchSize %s, defaulting to %s', + params: [undefined, DEFAULT_EVENT_BATCH_SIZE], + }])); + }); + + it('uses default size and adds a startup log if provided batchSize is less than 1', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + batchSize: -1, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(DEFAULT_EVENT_BATCH_SIZE); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.WARNING, + message: 'Invalid batchSize %s, defaulting to %s', + params: [-1, DEFAULT_EVENT_BATCH_SIZE], + }])); + }); + + it('does not use a failedEventRepeater if failedEventRetryInterval is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].failedEventRepeater).toBe(undefined); + }); + + it('uses a IntervalRepeater with provided failedEventRetryInterval as failedEventRepeater', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + failedEventRetryInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(Object.is(MockBatchEventProcessor.mock.calls[0][0].failedEventRepeater, MockIntervalRepeater.mock.instances[1])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(2, 12345); + }); + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = getMockEventDispatcher(); + const options = { + eventDispatcher, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('does not use any closingEventDispatcher if not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = getMockEventDispatcher(); + const options = { + eventDispatcher: getMockEventDispatcher(), + closingEventDispatcher, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + }); + + it('uses the provided eventStore', () => { + const eventStore = getMockSyncCache<EventWithId>(); + const options = { + eventDispatcher: getMockEventDispatcher(), + eventStore, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].eventStore).toBe(eventStore); + }); +}); diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts new file mode 100644 index 000000000..3e2cc0d7c --- /dev/null +++ b/lib/event_processor/event_processor_factory.ts @@ -0,0 +1,123 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogLevel } from "../common_exports"; +import { StartupLog } from "../service"; +import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import { EventDispatcher } from "./eventDispatcher"; +import { EventProcessor } from "./eventProcessor"; +import { BatchEventProcessor, EventWithId, RetryConfig } from "./batch_event_processor"; +import { AsyncPrefixCache, Cache, SyncPrefixCache } from "../utils/cache/cache"; + +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; +export const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; +export const DEFAULT_MIN_BACKOFF = 1000; +export const DEFAULT_MAX_BACKOFF = 32000; +export const FAILED_EVENT_RETRY_INTERVAL = 20 * 1000; +export const EVENT_STORE_PREFIX = 'optly_event:'; + +export const getPrefixEventStore = (cache: Cache<string>): Cache<EventWithId> => { + if (cache.operation === 'async') { + return new AsyncPrefixCache<string, EventWithId>( + cache, + EVENT_STORE_PREFIX, + JSON.parse, + JSON.stringify, + ); + } else { + return new SyncPrefixCache<string, EventWithId>( + cache, + EVENT_STORE_PREFIX, + JSON.parse, + JSON.stringify, + ); + } +}; + +export type BatchEventProcessorOptions = { + eventDispatcher?: EventDispatcher; + closingEventDispatcher?: EventDispatcher; + flushInterval?: number; + batchSize?: number; + eventStore?: Cache<string>; +}; + +export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, 'eventDispatcher' | 'eventStore'> & { + eventDispatcher: EventDispatcher; + failedEventRetryInterval?: number; + eventStore?: Cache<EventWithId>; + retryOptions?: { + maxRetries?: number; + minBackoff?: number; + maxBackoff?: number; + }; +} + +export const getBatchEventProcessor = ( + options: BatchEventProcessorFactoryOptions, + EventProcessorConstructor: typeof BatchEventProcessor = BatchEventProcessor + ): EventProcessor => { + const { eventDispatcher, closingEventDispatcher, retryOptions, eventStore } = options; + + const retryConfig: RetryConfig | undefined = retryOptions ? { + maxRetries: retryOptions.maxRetries, + backoffProvider: () => { + const minBackoff = retryOptions?.minBackoff ?? DEFAULT_MIN_BACKOFF; + const maxBackoff = retryOptions?.maxBackoff ?? DEFAULT_MAX_BACKOFF; + return new ExponentialBackoff(minBackoff, maxBackoff, 500); + } + } : undefined; + + const startupLogs: StartupLog[] = []; + + let flushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; + if (options.flushInterval === undefined || options.flushInterval <= 0) { + startupLogs.push({ + level: LogLevel.WARNING, + message: 'Invalid flushInterval %s, defaulting to %s', + params: [options.flushInterval, DEFAULT_EVENT_FLUSH_INTERVAL], + }); + } else { + flushInterval = options.flushInterval; + } + + let batchSize = DEFAULT_EVENT_BATCH_SIZE; + if (options.batchSize === undefined || options.batchSize <= 0) { + startupLogs.push({ + level: LogLevel.WARNING, + message: 'Invalid batchSize %s, defaulting to %s', + params: [options.batchSize, DEFAULT_EVENT_BATCH_SIZE], + }); + } else { + batchSize = options.batchSize; + } + + const dispatchRepeater = new IntervalRepeater(flushInterval); + const failedEventRepeater = options.failedEventRetryInterval ? + new IntervalRepeater(options.failedEventRetryInterval) : undefined; + + return new EventProcessorConstructor({ + eventDispatcher, + closingEventDispatcher, + dispatchRepeater, + failedEventRepeater, + retryConfig, + batchSize, + eventStore, + startupLogs, + }); +}; diff --git a/lib/event_processor/forwarding_event_processor.spec.ts b/lib/event_processor/forwarding_event_processor.spec.ts index 72da66633..41393109a 100644 --- a/lib/event_processor/forwarding_event_processor.spec.ts +++ b/lib/event_processor/forwarding_event_processor.spec.ts @@ -16,49 +16,10 @@ import { expect, describe, it, vi } from 'vitest'; import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher, makeBatchedEventV1 } from '.'; - -function createImpressionEvent() { - return { - type: 'impression' as const, - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } -} +import { EventDispatcher } from './eventDispatcher'; +import { formatEvents, makeBatchedEventV1 } from './v1/buildEventV1'; +import { createImpressionEvent } from '../tests/mock/create_event'; +import { ServiceState } from '../service'; const getMockEventDispatcher = (): EventDispatcher => { return { @@ -66,33 +27,94 @@ const getMockEventDispatcher = (): EventDispatcher => { }; }; -const getMockNotificationCenter = () => { - return { - sendNotifications: vi.fn(), - }; -} +describe('ForwardingEventProcessor', () => { + it('should resolve onRunning() when start is called', async () => { + const dispatcher = getMockEventDispatcher(); -describe('ForwardingEventProcessor', function() { - it('should dispatch event immediately when process is called', () => { + const processor = getForwardingEventProcessor(dispatcher); + + processor.start(); + await expect(processor.onRunning()).resolves.not.toThrow(); + }); + + it('should dispatch event immediately when process is called', async() => { const dispatcher = getMockEventDispatcher(); const mockDispatch = vi.mocked(dispatcher.dispatchEvent); - const notificationCenter = getMockNotificationCenter(); - const processor = getForwardingEventProcessor(dispatcher, notificationCenter); + + const processor = getForwardingEventProcessor(dispatcher); + processor.start(); + await processor.onRunning(); + const event = createImpressionEvent(); processor.process(event); expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); const data = mockDispatch.mock.calls[0][0].params; expect(data).toEqual(makeBatchedEventV1([event])); - expect(notificationCenter.sendNotifications).toHaveBeenCalledOnce(); }); - it('should return a resolved promise when stop is called', async () => { + it('should emit dispatch event when event is dispatched', async() => { + const dispatcher = getMockEventDispatcher(); + + const processor = getForwardingEventProcessor(dispatcher); + + processor.start(); + await processor.onRunning(); + + const listener = vi.fn(); + processor.onDispatch(listener); + + const event = createImpressionEvent(); + processor.process(event); + expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); + expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents([event])); + expect(listener).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(formatEvents([event])); + }); + + it('should remove dispatch listener when the function returned from onDispatch is called', async() => { const dispatcher = getMockEventDispatcher(); - const notificationCenter = getMockNotificationCenter(); - const processor = getForwardingEventProcessor(dispatcher, notificationCenter); + + const processor = getForwardingEventProcessor(dispatcher); + processor.start(); - const stopPromise = processor.stop(); - expect(stopPromise).resolves.not.toThrow(); + await processor.onRunning(); + + const listener = vi.fn(); + const unsub = processor.onDispatch(listener); + + let event = createImpressionEvent(); + processor.process(event); + expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); + expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents([event])); + expect(listener).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(formatEvents([event])); + + unsub(); + event = createImpressionEvent('id-a'); + processor.process(event); + expect(listener).toHaveBeenCalledOnce(); + }); + + it('should resolve onTerminated promise when stop is called', async () => { + const dispatcher = getMockEventDispatcher(); + const processor = getForwardingEventProcessor(dispatcher); + processor.start(); + await processor.onRunning(); + + expect(processor.getState()).toEqual(ServiceState.Running); + + processor.stop(); + await expect(processor.onTerminated()).resolves.not.toThrow(); + }); + + it('should reject onRunning promise when stop is called in New state', async () => { + const dispatcher = getMockEventDispatcher(); + const processor = getForwardingEventProcessor(dispatcher); + + expect(processor.getState()).toEqual(ServiceState.New); + + processor.stop(); + await expect(processor.onRunning()).rejects.toThrow(); }); }); diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 919710c53..1fc06ebc9 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -14,45 +14,58 @@ * limitations under the License. */ -import { - EventProcessor, - ProcessableEvent, -} from '.'; -import { NotificationSender } from '../core/notification_center'; + +import { EventV1Request } from './eventDispatcher'; +import { EventProcessor, ProcessableEvent } from './eventProcessor'; import { EventDispatcher } from '../shared_types'; -import { NOTIFICATION_TYPES } from '../utils/enums'; import { formatEvents } from '../core/event_builder/build_event_v1'; - -class ForwardingEventProcessor implements EventProcessor { +import { BaseService, ServiceState } from '../service'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; +import { Consumer, Fn } from '../utils/type'; +class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; - private NotificationSender?: NotificationSender; + private eventEmitter: EventEmitter<{ dispatch: EventV1Request }>; - constructor(dispatcher: EventDispatcher, notificationSender?: NotificationSender) { + constructor(dispatcher: EventDispatcher) { + super(); this.dispatcher = dispatcher; - this.NotificationSender = notificationSender; + this.eventEmitter = new EventEmitter(); } - process(event: ProcessableEvent): void { + process(event: ProcessableEvent): Promise<unknown> { const formattedEvent = formatEvents([event]); - this.dispatcher.dispatchEvent(formattedEvent).catch(() => {}); - if (this.NotificationSender) { - this.NotificationSender.sendNotifications( - NOTIFICATION_TYPES.LOG_EVENT, - formattedEvent, - ) - } + const res = this.dispatcher.dispatchEvent(formattedEvent); + this.eventEmitter.emit('dispatch', formattedEvent); + return res; } - start(): Promise<any> { - return Promise.resolve(); + start(): void { + if (!this.isNew()) { + return; + } + this.state = ServiceState.Running; + this.startPromise.resolve(); } - stop(): Promise<unknown> { - return Promise.resolve(); + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew()) { + this.startPromise.reject(new Error('Service stopped before it was started')); + } + + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + } + + onDispatch(handler: Consumer<EventV1Request>): Fn { + return this.eventEmitter.on('dispatch', handler); } } -export function getForwardingEventProcessor(dispatcher: EventDispatcher, notificationSender?: NotificationSender): EventProcessor { - return new ForwardingEventProcessor(dispatcher, notificationSender); +export function getForwardingEventProcessor(dispatcher: EventDispatcher): EventProcessor { + return new ForwardingEventProcessor(dispatcher); } diff --git a/lib/event_processor/index.react_native.ts b/lib/event_processor/index.react_native.ts deleted file mode 100644 index 27a6f3a3a..000000000 --- a/lib/event_processor/index.react_native.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor.react_native' diff --git a/lib/event_processor/index.ts b/lib/event_processor/index.ts deleted file mode 100644 index c91ca2d21..000000000 --- a/lib/event_processor/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor' diff --git a/lib/event_processor/managed.ts b/lib/event_processor/managed.ts deleted file mode 100644 index dfb94e0f5..000000000 --- a/lib/event_processor/managed.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export interface Managed { - start(): Promise<any> - - stop(): Promise<any> -} diff --git a/lib/event_processor/pendingEventsDispatcher.ts b/lib/event_processor/pendingEventsDispatcher.ts deleted file mode 100644 index cfa2c3e80..000000000 --- a/lib/event_processor/pendingEventsDispatcher.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../modules/logging' -import { EventDispatcher, EventV1Request, EventDispatcherResponse } from './eventDispatcher' -import { PendingEventsStore, LocalStorageStore } from './pendingEventsStore' -import { uuid, getTimestamp } from '../utils/fns' - -const logger = getLogger('EventProcessor') - -export type DispatcherEntry = { - uuid: string - timestamp: number - request: EventV1Request -} - -export class PendingEventsDispatcher implements EventDispatcher { - protected dispatcher: EventDispatcher - protected store: PendingEventsStore<DispatcherEntry> - - constructor({ - eventDispatcher, - store, - }: { - eventDispatcher: EventDispatcher - store: PendingEventsStore<DispatcherEntry> - }) { - this.dispatcher = eventDispatcher - this.store = store - } - - dispatchEvent(request: EventV1Request): Promise<EventDispatcherResponse> { - return this.send( - { - uuid: uuid(), - timestamp: getTimestamp(), - request, - } - ) - } - - sendPendingEvents(): void { - const pendingEvents = this.store.values() - - logger.debug('Sending %s pending events from previous page', pendingEvents.length) - - pendingEvents.forEach(item => { - this.send(item).catch((e) => { - logger.debug(String(e)); - }); - }) - } - - protected async send(entry: DispatcherEntry): Promise<EventDispatcherResponse> { - this.store.set(entry.uuid, entry) - - const response = await this.dispatcher.dispatchEvent(entry.request); - this.store.remove(entry.uuid); - return response; - } -} - -export class LocalStoragePendingEventsDispatcher extends PendingEventsDispatcher { - constructor({ eventDispatcher }: { eventDispatcher: EventDispatcher }) { - super({ - eventDispatcher, - store: new LocalStorageStore({ - // TODO make this configurable - maxValues: 100, - key: 'fs_optly_pending_events', - }), - }) - } -} diff --git a/lib/event_processor/pendingEventsStore.ts b/lib/event_processor/pendingEventsStore.ts deleted file mode 100644 index ca8dbf0f7..000000000 --- a/lib/event_processor/pendingEventsStore.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { objectValues } from '../utils/fns' -import { getLogger } from '../modules/logging'; - -const logger = getLogger('EventProcessor') - -export interface PendingEventsStore<K> { - get(key: string): K | null - - set(key: string, value: K): void - - remove(key: string): void - - values(): K[] - - clear(): void - - replace(newMap: { [key: string]: K }): void -} - -interface StoreEntry { - uuid: string - timestamp: number -} - -export class LocalStorageStore<K extends StoreEntry> implements PendingEventsStore<K> { - protected LS_KEY: string - protected maxValues: number - - constructor({ key, maxValues = 1000 }: { key: string; maxValues?: number }) { - this.LS_KEY = key - this.maxValues = maxValues - } - - get(key: string): K | null { - return this.getMap()[key] || null - } - - set(key: string, value: K): void { - const map = this.getMap() - map[key] = value - this.replace(map) - } - - remove(key: string): void { - const map = this.getMap() - delete map[key] - this.replace(map) - } - - values(): K[] { - return objectValues(this.getMap()) - } - - clear(): void { - this.replace({}) - } - - replace(map: { [key: string]: K }): void { - try { - // This is a temporary fix to support React Native which does not have localStorage. - typeof window !== 'undefined' ? window && window.localStorage && localStorage.setItem(this.LS_KEY, JSON.stringify(map)) : localStorage.setItem(this.LS_KEY, JSON.stringify(map)) - this.clean() - } catch (e) { - logger.error(String(e)) - } - } - - private clean() { - const map = this.getMap() - const keys = Object.keys(map) - const toRemove = keys.length - this.maxValues - if (toRemove < 1) { - return - } - - const entries = keys.map(key => ({ - key, - value: map[key] - })) - - entries.sort((a, b) => a.value.timestamp - b.value.timestamp) - - for (let i = 0; i < toRemove; i++) { - delete map[entries[i].key] - } - - this.replace(map) - } - - private getMap(): { [key: string]: K } { - try { - // This is a temporary fix to support React Native which does not have localStorage. - const data = typeof window !== 'undefined' ? window && window.localStorage && localStorage.getItem(this.LS_KEY): localStorage.getItem(this.LS_KEY); - if (data) { - return (JSON.parse(data) as { [key: string]: K }) || {} - } - } catch (e: any) { - logger.error(e) - } - return {} - } -} diff --git a/lib/event_processor/reactNativeEventsStore.ts b/lib/event_processor/reactNativeEventsStore.ts deleted file mode 100644 index cf7dce9c8..000000000 --- a/lib/event_processor/reactNativeEventsStore.ts +++ /dev/null @@ -1,84 +0,0 @@ - -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../modules/logging' -import { objectValues } from '../utils/fns' - -import { Synchronizer } from './synchronizer' -import ReactNativeAsyncStorageCache from '../plugins/key_value_cache/reactNativeAsyncStorageCache'; -import PersistentKeyValueCache from '../plugins/key_value_cache/persistentKeyValueCache'; - -const logger = getLogger('ReactNativeEventsStore') - -/** - * A key value store which stores objects of type T with string keys - */ -export class ReactNativeEventsStore<T> { - private maxSize: number - private storeKey: string - private synchronizer: Synchronizer = new Synchronizer() - private cache: PersistentKeyValueCache; - - constructor(maxSize: number, storeKey: string, cache?: PersistentKeyValueCache) { - this.maxSize = maxSize - this.storeKey = storeKey - this.cache = cache || new ReactNativeAsyncStorageCache() - } - - public async set(key: string, event: T): Promise<string> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.getEventsMap(); - if (Object.keys(eventsMap).length < this.maxSize) { - eventsMap[key] = event - await this.cache.set(this.storeKey, JSON.stringify(eventsMap)) - } else { - logger.warn('React native events store is full. Store key: %s', this.storeKey) - } - this.synchronizer.releaseLock() - return key - } - - public async get(key: string): Promise<T> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.getEventsMap() - this.synchronizer.releaseLock() - return eventsMap[key] - } - - public async getEventsMap(): Promise<{[key: string]: T}> { - const cachedValue = await this.cache.get(this.storeKey) || '{}'; - return JSON.parse(cachedValue) - } - - public async getEventsList(): Promise<T[]> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.getEventsMap() - this.synchronizer.releaseLock() - return objectValues(eventsMap) - } - - public async remove(key: string): Promise<void> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.getEventsMap() - eventsMap[key] && delete eventsMap[key] - await this.cache.set(this.storeKey, JSON.stringify(eventsMap)) - this.synchronizer.releaseLock() - } - - public async clear(): Promise<void> { - await this.cache.remove(this.storeKey) - } -} diff --git a/lib/event_processor/requestTracker.ts b/lib/event_processor/requestTracker.ts deleted file mode 100644 index 192919884..000000000 --- a/lib/event_processor/requestTracker.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * RequestTracker keeps track of in-flight requests for EventProcessor using - * an internal counter. It exposes methods for adding a new request to be - * tracked, and getting a Promise representing the completion of currently - * tracked requests. - */ -class RequestTracker { - private reqsInFlightCount = 0 - private reqsCompleteResolvers: Array<() => void> = [] - - /** - * Track the argument request (represented by a Promise). reqPromise will feed - * into the state of Promises returned by onRequestsComplete. - * @param {Promise<void>} reqPromise - */ - public trackRequest(reqPromise: Promise<void>): void { - this.reqsInFlightCount++ - const onReqComplete = () => { - this.reqsInFlightCount-- - if (this.reqsInFlightCount === 0) { - this.reqsCompleteResolvers.forEach(resolver => resolver()) - this.reqsCompleteResolvers = [] - } - } - reqPromise.then(onReqComplete, onReqComplete) - } - - /** - * Return a Promise that fulfills after all currently-tracked request promises - * are resolved. - * @return {Promise<void>} - */ - public onRequestsComplete(): Promise<void> { - return new Promise(resolve => { - if (this.reqsInFlightCount === 0) { - resolve() - } else { - this.reqsCompleteResolvers.push(resolve) - } - }) - } -} - -export default RequestTracker diff --git a/lib/event_processor/synchronizer.ts b/lib/event_processor/synchronizer.ts deleted file mode 100644 index f0659d7af..000000000 --- a/lib/event_processor/synchronizer.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This synchronizer makes sure the operations are atomic using promises. - */ -export class Synchronizer { - private lockPromises: Promise<void>[] = [] - private resolvers: any[] = [] - - // Adds a promise to the existing list and returns the promise so that the code block can wait for its turn - public async getLock(): Promise<void> { - this.lockPromises.push(new Promise(resolve => this.resolvers.push(resolve))) - if (this.lockPromises.length === 1) { - return - } - await this.lockPromises[this.lockPromises.length - 2] - } - - // Resolves first promise in the array so that the code block waiting on the first promise can continue execution - public releaseLock(): void { - if (this.lockPromises.length > 0) { - this.lockPromises.shift() - const resolver = this.resolvers.shift() - resolver() - return - } - } -} diff --git a/lib/event_processor/v1/v1EventProcessor.react_native.ts b/lib/event_processor/v1/v1EventProcessor.react_native.ts deleted file mode 100644 index f4998a37b..000000000 --- a/lib/event_processor/v1/v1EventProcessor.react_native.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - uuid as id, - objectEntries, -} from '../../utils/fns' -import { - NetInfoState, - addEventListener as addConnectionListener, -} from "@react-native-community/netinfo" -import { getLogger } from '../../modules/logging' -import { NotificationSender } from '../../core/notification_center' - -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from "../eventProcessor" -import { ReactNativeEventsStore } from '../reactNativeEventsStore' -import { Synchronizer } from '../synchronizer' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' -import { - EventV1Request, - EventDispatcher, -} from '../eventDispatcher' -import { PersistentCacheProvider } from '../../shared_types' - -const logger = getLogger('ReactNativeEventProcessor') - -const DEFAULT_MAX_QUEUE_SIZE = 10000 -const PENDING_EVENTS_STORE_KEY = 'fs_optly_pending_events' -const EVENT_BUFFER_STORE_KEY = 'fs_optly_event_buffer' - -/** - * React Native Events Processor with Caching support for events when app is offline. - */ -export class LogTierV1EventProcessor implements EventProcessor { - private id = Math.random(); - private dispatcher: EventDispatcher - // expose for testing - public queue: EventQueue<ProcessableEvent> - private notificationSender?: NotificationSender - private requestTracker: RequestTracker - - /* eslint-disable */ - private unsubscribeNetInfo: Function | null = null - /* eslint-enable */ - private isInternetReachable = true - private pendingEventsPromise: Promise<void> | null = null - private synchronizer: Synchronizer = new Synchronizer() - - // If a pending event fails to dispatch, this indicates skipping further events to preserve sequence in the next retry. - private shouldSkipDispatchToPreserveSequence = false - - /** - * This Stores Formatted events before dispatching. The events are removed after they are successfully dispatched. - * Stored events are retried on every new event dispatch, when connection becomes available again or when SDK initializes the next time. - */ - private pendingEventsStore: ReactNativeEventsStore<EventV1Request> - - /** - * This stores individual events generated from the SDK till they are part of the pending buffer. - * The store is cleared right before the event is formatted to be dispatched. - * This is to make sure that individual events are not lost when app closes before the buffer was flushed. - */ - private eventBufferStore: ReactNativeEventsStore<ProcessableEvent> - - constructor({ - dispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - maxQueueSize = DEFAULT_MAX_QUEUE_SIZE, - notificationCenter, - persistentCacheProvider, - }: { - dispatcher: EventDispatcher - flushInterval?: number - batchSize?: number - maxQueueSize?: number - notificationCenter?: NotificationSender - persistentCacheProvider?: PersistentCacheProvider - }) { - this.dispatcher = dispatcher - this.notificationSender = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, areEventContextsEqual, this.drainQueue.bind(this)) - this.pendingEventsStore = new ReactNativeEventsStore( - maxQueueSize, - PENDING_EVENTS_STORE_KEY, - persistentCacheProvider && persistentCacheProvider(), - ); - this.eventBufferStore = new ReactNativeEventsStore( - maxQueueSize, - EVENT_BUFFER_STORE_KEY, - persistentCacheProvider && persistentCacheProvider(), - ) - } - - private async connectionListener(state: NetInfoState) { - if (this.isInternetReachable && !state.isInternetReachable) { - this.isInternetReachable = false - logger.debug('Internet connection lost') - return - } - if (!this.isInternetReachable && state.isInternetReachable) { - this.isInternetReachable = true - logger.debug('Internet connection is restored, attempting to dispatch pending events') - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - } - } - - private isSuccessResponse(status: number): boolean { - return status >= 200 && status < 400 - } - - private async drainQueue(buffer: ProcessableEvent[]): Promise<void> { - if (buffer.length === 0) { - return - } - - await this.synchronizer.getLock() - - // Retry pending failed events while draining queue - await this.processPendingEvents() - logger.debug('draining queue with %s events', buffer.length) - - const eventCacheKey = id() - const formattedEvent = formatEvents(buffer) - - // Store formatted event before dispatching to be retried later in case of failure. - await this.pendingEventsStore.set(eventCacheKey, formattedEvent) - - // Clear buffer because the buffer has become a formatted event and is already stored in pending cache. - for (const {uuid} of buffer) { - await this.eventBufferStore.remove(uuid) - } - - if (!this.shouldSkipDispatchToPreserveSequence) { - await this.dispatchEvent(eventCacheKey, formattedEvent) - } - - // Resetting skip flag because current sequence of events have all been processed - this.shouldSkipDispatchToPreserveSequence = false - - this.synchronizer.releaseLock() - } - - private async processPendingEvents(): Promise<void> { - logger.debug('Processing pending events from offline storage') - if (!this.pendingEventsPromise) { - // Only process events if existing promise is not in progress - this.pendingEventsPromise = this.getPendingEventsPromise() - } else { - logger.debug('Already processing pending events, returning the existing promise') - } - await this.pendingEventsPromise - this.pendingEventsPromise = null - } - - private async getPendingEventsPromise(): Promise<void> { - const formattedEvents: {[key: string]: any} = await this.pendingEventsStore.getEventsMap() - const eventEntries = objectEntries(formattedEvents) - logger.debug('Processing %s pending events', eventEntries.length) - // Using for loop to be able to wait for previous dispatch to finish before moving on to the new one - for (const [eventKey, event] of eventEntries) { - // If one event dispatch failed, skip subsequent events to preserve sequence - if (this.shouldSkipDispatchToPreserveSequence) { - return - } - await this.dispatchEvent(eventKey, event) - } - } - - private async dispatchEvent(eventCacheKey: string, event: EventV1Request): Promise<void> { - const requestPromise = new Promise<void>((resolve) => { - this.dispatcher.dispatchEvent(event).then((response) => { - if (!response.statusCode || this.isSuccessResponse(response.statusCode)) { - return this.pendingEventsStore.remove(eventCacheKey) - } else { - this.shouldSkipDispatchToPreserveSequence = true - logger.warn('Failed to dispatch event, Response status Code: %s', response.statusCode) - return Promise.resolve() - } - }).catch((e) => { - logger.warn('Failed to dispatch event, error: %s', e.message) - }).finally(() => resolve()) - - sendEventNotification(this.notificationSender, event) - }) - // Tracking all the requests to dispatch to make sure request is completed before fulfilling the `stop` promise - this.requestTracker.trackRequest(requestPromise) - return requestPromise - } - - public async start(): Promise<void> { - await this.queue.start() - this.unsubscribeNetInfo = addConnectionListener(this.connectionListener.bind(this)) - - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - - // Process individual events pending from the buffer. - const events: ProcessableEvent[] = await this.eventBufferStore.getEventsList() - await this.eventBufferStore.clear() - events.forEach(this.process.bind(this)) - } - - public process(event: ProcessableEvent): void { - // Adding events to buffer store. If app closes before dispatch, we can reprocess next time the app initializes - this.eventBufferStore.set(event.uuid, event).then(() => { - this.queue.enqueue(event) - }) - } - - public async stop(): Promise<void> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.unsubscribeNetInfo && this.unsubscribeNetInfo() - await this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e) { - logger.error('Error stopping EventProcessor: "%s"', Object(e).message, String(e)) - } - } -} diff --git a/lib/event_processor/v1/v1EventProcessor.ts b/lib/event_processor/v1/v1EventProcessor.ts deleted file mode 100644 index aac5103ef..000000000 --- a/lib/event_processor/v1/v1EventProcessor.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../../modules/logging' -import { NotificationSender } from '../../core/notification_center' - -import { EventDispatcher } from '../eventDispatcher' -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from '../eventProcessor' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' - -const logger = getLogger('LogTierV1EventProcessor') - -export class LogTierV1EventProcessor implements EventProcessor { - private dispatcher: EventDispatcher - private closingDispatcher?: EventDispatcher - private queue: EventQueue<ProcessableEvent> - private notificationCenter?: NotificationSender - private requestTracker: RequestTracker - - constructor({ - dispatcher, - closingDispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - notificationCenter, - }: { - dispatcher: EventDispatcher - closingDispatcher?: EventDispatcher - flushInterval?: number - batchSize?: number - notificationCenter?: NotificationSender - }) { - this.dispatcher = dispatcher - this.closingDispatcher = closingDispatcher - this.notificationCenter = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue( - batchSize, - flushInterval, - areEventContextsEqual, - this.drainQueue.bind(this, false), - this.drainQueue.bind(this, true), - ); - } - - private drainQueue(useClosingDispatcher: boolean, buffer: ProcessableEvent[]): Promise<void> { - const reqPromise = new Promise<void>(resolve => { - logger.debug('draining queue with %s events', buffer.length) - - if (buffer.length === 0) { - resolve() - return - } - - const formattedEvent = formatEvents(buffer) - const dispatcher = useClosingDispatcher && this.closingDispatcher - ? this.closingDispatcher : this.dispatcher; - - // TODO: this does not do anything if the dispatcher fails - // to dispatch. What should be done in that case? - dispatcher.dispatchEvent(formattedEvent).finally(() => { - resolve() - }) - sendEventNotification(this.notificationCenter, formattedEvent) - }) - this.requestTracker.trackRequest(reqPromise) - return reqPromise - } - - process(event: ProcessableEvent): void { - this.queue.enqueue(event) - } - - // TODO[OASIS-6649]: Don't use any type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - stop(): Promise<any> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e) { - logger.error('Error stopping EventProcessor: "%s"', Object(e).message, String(e)) - } - return Promise.resolve() - } - - async start(): Promise<void> { - await this.queue.start() - } -} diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 3d3952189..3d38655ed 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -18,13 +18,11 @@ import logging, { getLogger } from './modules/logging/logger'; import { assert } from 'chai'; import sinon from 'sinon'; -import { default as eventProcessor } from './plugins/event_processor'; import Optimizely from './optimizely'; import testData from './tests/test_data'; import packageJSON from '../package.json'; import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import OptimizelyUserContext from './optimizely_user_context'; import { LOG_MESSAGES, ODP_EVENT_ACTION } from './utils/enums'; @@ -36,7 +34,6 @@ import { OdpEvent } from './core/odp/odp_event'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; -var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; class MockLocalStorage { store = {}; @@ -110,12 +107,9 @@ describe('javascript-sdk (Browser)', function() { sinon.stub(configValidator, 'validate'); global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); - - sinon.stub(LocalStoragePendingEventsDispatcher.prototype, 'sendPendingEvents'); }); afterEach(function() { - LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents.restore(); optimizelyFactory.__internalResetRetryState(); console.error.restore(); configValidator.validate.restore(); @@ -143,8 +137,6 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, logger: silentLogger, }); - - sinon.assert.notCalled(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); }); }); diff --git a/lib/index.browser.ts b/lib/index.browser.ts index fd92d72c9..f7b7ba98c 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -16,16 +16,13 @@ import logHelper from './modules/logging/logger'; import { getLogger, setErrorHandler, getErrorHandler, LogLevel } from './modules/logging'; -import { LocalStoragePendingEventsDispatcher } from './event_processor'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/default_dispatcher.browser'; import sendBeaconEventDispatcher from './plugins/event_dispatcher/send_beacon_dispatcher'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import { createNotificationCenter } from './core/notification_center'; -import { default as eventProcessor } from './plugins/event_processor'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import Optimizely from './optimizely'; @@ -34,7 +31,7 @@ import { getUserAgentParser } from './plugins/odp/user_agent_parser/index.browse import * as commonExports from './common_exports'; import { PollingConfigManagerConfig } from './project_config/config_manager_factory'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; -import { createForwardingEventProcessor } from './event_processor/event_processor_factory.browser'; +import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.browser'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -199,6 +196,7 @@ export { getUserAgentParser, createPollingProjectConfigManager, createForwardingEventProcessor, + createBatchEventProcessor, }; export * from './common_exports'; @@ -218,6 +216,7 @@ export default { getUserAgentParser, createPollingProjectConfigManager, createForwardingEventProcessor, + createBatchEventProcessor, }; export * from './export_types'; diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 8ff0edeff..aa0f8743e 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -15,7 +15,6 @@ */ import { assert } from 'chai'; import sinon from 'sinon'; -import * as eventProcessor from './plugins/event_processor'; import * as enums from './utils/enums'; import Optimizely from './optimizely'; @@ -54,17 +53,17 @@ describe('optimizelyFactory', function() { console.error.restore(); }); - it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { - configValidator.validate.throws(new Error('Invalid config or something')); - var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); - assert.doesNotThrow(function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - logger: localLogger, - }); - }); - sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); - }); + // it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { + // configValidator.validate.throws(new Error('Invalid config or something')); + // var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); + // assert.doesNotThrow(function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager(), + // logger: localLogger, + // }); + // }); + // sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); + // }); it('should not throw if the provided config is not valid and log an error if no logger is provided', function() { configValidator.validate.throws(new Error('Invalid config or something')); diff --git a/lib/index.node.ts b/lib/index.node.ts index 98efc5d64..ba4290d53 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -21,14 +21,12 @@ import * as loggerPlugin from './plugins/logger'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/default_dispatcher.node'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import { createNotificationCenter } from './core/notification_center'; -import { createEventProcessor } from './plugins/event_processor'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { NodeOdpManager } from './plugins/odp_manager/index.node'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; -import { createForwardingEventProcessor } from './event_processor/event_processor_factory.node'; +import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; const logger = getLogger(); setLogLevel(LogLevel.ERROR); @@ -145,6 +143,7 @@ export { OptimizelyDecideOption, createPollingProjectConfigManager, createForwardingEventProcessor, + createBatchEventProcessor, }; export * from './common_exports'; @@ -161,6 +160,7 @@ export default { OptimizelyDecideOption, createPollingProjectConfigManager, createForwardingEventProcessor, + createBatchEventProcessor, }; export * from './export_types'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index b2654823d..41cf71369 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -21,14 +21,12 @@ import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import * as loggerPlugin from './plugins/logger/index.react_native'; import defaultEventDispatcher from './event_processor/default_dispatcher.browser'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; import { createNotificationCenter } from './core/notification_center'; -import { createEventProcessor } from './plugins/event_processor/index.react_native'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; -import { createForwardingEventProcessor } from './event_processor/event_processor_factory.react_native'; +import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.react_native'; import 'fast-text-encoding'; import 'react-native-get-random-values'; @@ -148,6 +146,7 @@ export { OptimizelyDecideOption, createPollingProjectConfigManager, createForwardingEventProcessor, + createBatchEventProcessor, }; export * from './common_exports'; @@ -164,6 +163,7 @@ export default { OptimizelyDecideOption, createPollingProjectConfigManager, createForwardingEventProcessor, + createBatchEventProcessor, }; export * from './export_types'; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index ca375151b..f0dd8e00e 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -34,7 +34,6 @@ import * as jsonSchemaValidator from '../utils/json_schema_validator'; import * as projectConfig from '../project_config/project_config'; import testData from '../tests/test_data'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; -import { createEventProcessor } from '../plugins/event_processor'; import { createNotificationCenter } from '../core/notification_center'; import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; @@ -60,6 +59,34 @@ const getMockEventProcessor = (notificationCenter) => { return getForwardingEventProcessor(getMockEventDispatcher(), notificationCenter); } +const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(datafileObj), + }); + const eventDispatcher = getMockEventDispatcher(); + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + + const notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); + + const optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: mockConfigManager, + errorHandler: errorHandler, + eventProcessor, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: defaultDecideOptions || [], + notificationCenter, + }); + + sinon.stub(notificationCenter, 'sendNotifications'); + + return { optlyInstance, eventProcessor, eventDispatcher, notificationCenter, createdLogger } +} + describe('lib/optimizely', function() { var ProjectConfigManagerStub; var globalStubErrorHandler; @@ -4474,11 +4501,9 @@ describe('lib/optimizely', function() { }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); var eventDispatcher = getMockEventDispatcher(); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); describe('#createUserContext', function() { beforeEach(function() { @@ -4591,26 +4616,14 @@ describe('lib/optimizely', function() { describe('#decide', function() { var userId = 'tester'; describe('with empty default decide options', function() { + let optlyInstance, notificationCenter, createdLogger; beforeEach(function() { - const mockConfigManager = getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }); + + ({ optlyInstance, notificationCenter, createdLogger, eventDispatcher} = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + })); - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: mockConfigManager, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [], - notificationCenter, - eventProcessor, - }); - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); sinon.stub(errorHandler, 'handleError'); sinon.stub(createdLogger, 'log'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); @@ -4621,7 +4634,7 @@ describe('lib/optimizely', function() { errorHandler.handleError.restore(); createdLogger.log.restore(); fns.uuid.restore(); - optlyInstance.notificationCenter.sendNotifications.restore(); + notificationCenter.sendNotifications.restore(); }); it('should return error decision object when provided flagKey is invalid and do not dispatch an event', function() { @@ -4738,8 +4751,8 @@ describe('lib/optimizely', function() { }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 4); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(3).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4779,8 +4792,8 @@ describe('lib/optimizely', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(1).args; + sinon.assert.calledTwice(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(1).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4822,8 +4835,8 @@ describe('lib/optimizely', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(0).args; + sinon.assert.calledOnce(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(0).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4845,6 +4858,11 @@ describe('lib/optimizely', function() { }); it('should make a decision for rollout and dispatch an event when sendFlagDecisions is set to true', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ) var flagKey = 'feature_1'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4863,8 +4881,8 @@ describe('lib/optimizely', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 4); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(3).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4886,6 +4904,12 @@ describe('lib/optimizely', function() { }); it('should make a decision for rollout and do not dispatch an event when sendFlagDecisions is set to false', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ) + var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = false; optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); @@ -4907,8 +4931,8 @@ describe('lib/optimizely', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(1).args; + sinon.assert.calledTwice(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(1).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4930,6 +4954,11 @@ describe('lib/optimizely', function() { }); it('should make a decision when variation is null and dispatch an event', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ) var flagKey = 'feature_3'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4948,8 +4977,8 @@ describe('lib/optimizely', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 4); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(3).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4972,40 +5001,11 @@ describe('lib/optimizely', function() { }); describe('with EXCLUDE_VARIABLES flag in default decide options', function() { - beforeEach(function() { - const mockConfigManager = getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }); - - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: mockConfigManager, - errorHandler: errorHandler, - eventProcessor, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], - eventProcessor, - notificationCenter, - }); - - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); - sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - }); - - afterEach(function() { - eventDispatcher.dispatchEvent.reset(); - optlyInstance.notificationCenter.sendNotifications.restore(); - errorHandler.handleError.restore(); - createdLogger.log.restore(); - fns.uuid.restore(); - }); - it('should exclude variables in decision object and dispatch an event', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], + }) var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -5023,8 +5023,8 @@ describe('lib/optimizely', function() { }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.calledThrice(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.calledThrice(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -5046,6 +5046,11 @@ describe('lib/optimizely', function() { }); it('should exclude variables in decision object and do not dispatch an event when DISABLE_DECISION_EVENT is passed in decide options', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], + }) + var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -5063,8 +5068,8 @@ describe('lib/optimizely', function() { }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(0).args; + sinon.assert.calledOnce(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(0).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -5779,40 +5784,15 @@ describe('lib/optimizely', function() { }); }); + describe('#decideForKeys', function() { var userId = 'tester'; - beforeEach(function() { - eventDispatcher.dispatchEvent.reset(); - const mockConfigManager = getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }); - - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: mockConfigManager, - errorHandler: errorHandler, - eventProcessor, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [], - notificationCenter, - eventProcessor, - }); - - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - }); - - afterEach(function() { - eventDispatcher.dispatchEvent.reset(); - optlyInstance.notificationCenter.sendNotifications.restore(); - }); - it('should return decision results map with single flag key provided for feature_test and dispatch an event', function() { var flagKey = 'feature_2'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var user = optlyInstance.createUserContext(userId); var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); + var decisionsMap = optlyInstance.decideForKeys(user, [flagKey]); var decision = decisionsMap[flagKey]; var expectedDecision = { @@ -5835,7 +5815,9 @@ describe('lib/optimizely', function() { it('should return decision results map with two flag keys provided and dispatch events', function() { var flagKeysArray = ['feature_1', 'feature_2']; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var user = optlyInstance.createUserContext(userId); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKeysArray[0], userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKeysArray[1], userId); var decisionsMap = optlyInstance.decideForKeys(user, flagKeysArray); @@ -5868,6 +5850,7 @@ describe('lib/optimizely', function() { it('should return decision results map with only enabled flags when ENABLED_FLAGS_ONLY flag is passed in and dispatch events', function() { var flagKey1 = 'feature_2'; var flagKey2 = 'feature_3'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey1, userId); var decisionsMap = optlyInstance.decideForKeys( @@ -5894,36 +5877,11 @@ describe('lib/optimizely', function() { describe('#decideAll', function() { var userId = 'tester'; describe('with empty default decide options', function() { - beforeEach(function() { - const mockConfigManager = getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }); - - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: mockConfigManager, - errorHandler: errorHandler, - eventProcessor, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [], - notificationCenter, - eventProcessor, - }); - - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - }); - - afterEach(function() { - eventDispatcher.dispatchEvent.reset(); - optlyInstance.notificationCenter.sendNotifications.restore(); - }); it('should return decision results map with all flag keys provided and dispatch events', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var configObj = optlyInstance.projectConfigManager.getConfig(); - var allFlagKeysArray = Object.keys(configObj.featureKeyMap); + var allFlagKeysArray = Object.keys(configObj.featureKeyMap); var user = optlyInstance.createUserContext(userId); var expectedVariables1 = optlyInstance.getAllFeatureVariables(allFlagKeysArray[0], userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(allFlagKeysArray[1], userId); @@ -5969,6 +5927,7 @@ describe('lib/optimizely', function() { it('should return decision results map with only enabled flags when ENABLED_FLAGS_ONLY flag is passed in and dispatch events', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKey1, userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKey2, userId); @@ -6001,35 +5960,13 @@ describe('lib/optimizely', function() { }); describe('with ENABLED_FLAGS_ONLY flag in default decide options', function() { - beforeEach(function() { - const mockConfigManager = getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }); - - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: mockConfigManager, - errorHandler: errorHandler, - eventProcessor, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY], - notificationCenter, - }); - - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - }); - - afterEach(function() { - eventDispatcher.dispatchEvent.reset(); - optlyInstance.notificationCenter.sendNotifications.restore(); - }); - it('should return decision results map with only enabled flags and dispatch events', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY] + }); var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKey1, userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKey2, userId); @@ -6063,6 +6000,12 @@ describe('lib/optimizely', function() { it('should return decision results map with only enabled flags and excluded variables when EXCLUDE_VARIABLES_FLAG is passed in', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; + + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY] + }); + var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var decisionsMap = optlyInstance.decideAll(user, [OptimizelyDecideOption.EXCLUDE_VARIABLES]); var decision1 = decisionsMap[flagKey1]; @@ -6085,6 +6028,7 @@ describe('lib/optimizely', function() { userContext: user, reasons: [], }; + console.log(decisionsMap); assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -6103,11 +6047,9 @@ describe('lib/optimizely', function() { }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); var eventDispatcher = getMockEventDispatcher(); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), @@ -6177,11 +6119,9 @@ describe('lib/optimizely', function() { dispatchEvent: () => Promise.resolve({ statusCode: 200 }), }; - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { const mockConfigManager = getMockProjectConfigManager({ @@ -9023,11 +8963,9 @@ describe('lib/optimizely', function() { var eventDispatcher = { dispatchEvent: () => Promise.resolve({ statusCode: 200 }), }; - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTypedAudiencesConfig()), @@ -9171,11 +9109,9 @@ describe('lib/optimizely', function() { var eventDispatcher = { dispatchEvent: () => Promise.resolve({ statusCode: 200 }), }; - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTypedAudiencesConfig()), @@ -9377,12 +9313,9 @@ describe('lib/optimizely', function() { sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); eventDispatcher = getMockEventDispatcher(); - eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 3, - notificationCenter: notificationCenter, - flushInterval: 100, - }); + eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); }); afterEach(function() { @@ -9393,293 +9326,294 @@ describe('lib/optimizely', function() { fns.uuid.restore(); }); - describe('when eventBatchSize = 3 and eventFlushInterval = 100', function() { - var optlyInstance; - - beforeEach(function() { - const mockConfigManager = getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfig()), - }); - - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: mockConfigManager, - errorHandler: errorHandler, - eventProcessor, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 3, - eventFlushInterval: 100, - eventProcessor, - notificationCenter, - }); - }); - - afterEach(function() { - optlyInstance.close(); - }); - - it('should send batched events when the maxQueueSize is reached', function() { - fakeDecisionResponse = { - result: '111129', - reasons: [], - }; - bucketStub.returns(fakeDecisionResponse); - var activate = optlyInstance.activate('testExperiment', 'testUser'); - assert.strictEqual(activate, 'variation'); - - optlyInstance.track('testEvent', 'testUser'); - optlyInstance.track('testEvent', 'testUser'); - - sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - var expectedObj = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: '4', - experiment_id: '111127', - variation_id: '111129', - metadata: { - flag_key: '', - rule_key: 'testExperiment', - rule_type: 'experiment', - variation_key: 'variation', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: '4', - timestamp: Math.round(new Date().getTime()), - key: 'campaign_activated', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - attributes: [], - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: new Date().getTime(), - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: new Date().getTime(), - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: enums.CLIENT_VERSION, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - assert.deepEqual(eventDispatcherCall[0], expectedObj); - }); - - it('should flush the queue when the flushInterval occurs', function() { - var timestamp = new Date().getTime(); - fakeDecisionResponse = { - result: '111129', - reasons: [], - }; - bucketStub.returns(fakeDecisionResponse); - var activate = optlyInstance.activate('testExperiment', 'testUser'); - assert.strictEqual(activate, 'variation'); - - optlyInstance.track('testEvent', 'testUser'); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); - - clock.tick(100); - - sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - var expectedObj = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: '4', - experiment_id: '111127', - variation_id: '111129', - metadata: { - flag_key: '', - rule_key: 'testExperiment', - rule_type: 'experiment', - variation_key: 'variation', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: '4', - timestamp: timestamp, - key: 'campaign_activated', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - attributes: [], - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: timestamp, - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: enums.CLIENT_VERSION, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - assert.deepEqual(eventDispatcherCall[0], expectedObj); - }); - - it('should flush the queue when optimizely.close() is called', function() { - fakeDecisionResponse = { - result: '111129', - reasons: [], - }; - bucketStub.returns(fakeDecisionResponse); - var activate = optlyInstance.activate('testExperiment', 'testUser'); - assert.strictEqual(activate, 'variation'); - - optlyInstance.track('testEvent', 'testUser'); + // TODO: these tests does not belong here, these belong in EventProcessor tests + // describe('when eventBatchSize = 3 and eventFlushInterval = 100', function() { + // var optlyInstance; + + // beforeEach(function() { + // const mockConfigManager = getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfig()), + // }); + + // optlyInstance = new Optimizely({ + // clientEngine: 'node-sdk', + // projectConfigManager: mockConfigManager, + // errorHandler: errorHandler, + // eventProcessor, + // jsonSchemaValidator: jsonSchemaValidator, + // logger: createdLogger, + // isValidInstance: true, + // eventBatchSize: 3, + // eventFlushInterval: 100, + // eventProcessor, + // notificationCenter, + // }); + // }); - sinon.assert.notCalled(eventDispatcher.dispatchEvent); + // afterEach(function() { + // optlyInstance.close(); + // }); - optlyInstance.close(); + // it('should send batched events when the maxQueueSize is reached', function() { + // fakeDecisionResponse = { + // result: '111129', + // reasons: [], + // }; + // bucketStub.returns(fakeDecisionResponse); + // var activate = optlyInstance.activate('testExperiment', 'testUser'); + // assert.strictEqual(activate, 'variation'); + + // optlyInstance.track('testEvent', 'testUser'); + // optlyInstance.track('testEvent', 'testUser'); + + // sinon.assert.calledOnce(eventDispatcher.dispatchEvent); + + // var expectedObj = { + // url: 'https://logx.optimizely.com/v1/events', + // httpVerb: 'POST', + // params: { + // account_id: '12001', + // project_id: '111001', + // visitors: [ + // { + // snapshots: [ + // { + // decisions: [ + // { + // campaign_id: '4', + // experiment_id: '111127', + // variation_id: '111129', + // metadata: { + // flag_key: '', + // rule_key: 'testExperiment', + // rule_type: 'experiment', + // variation_key: 'variation', + // enabled: true, + // }, + // }, + // ], + // events: [ + // { + // entity_id: '4', + // timestamp: Math.round(new Date().getTime()), + // key: 'campaign_activated', + // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + // }, + // ], + // }, + // ], + // visitor_id: 'testUser', + // attributes: [], + // }, + // { + // attributes: [], + // snapshots: [ + // { + // events: [ + // { + // entity_id: '111095', + // key: 'testEvent', + // timestamp: new Date().getTime(), + // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + // }, + // ], + // }, + // ], + // visitor_id: 'testUser', + // }, + // { + // attributes: [], + // snapshots: [ + // { + // events: [ + // { + // entity_id: '111095', + // key: 'testEvent', + // timestamp: new Date().getTime(), + // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + // }, + // ], + // }, + // ], + // visitor_id: 'testUser', + // }, + // ], + // revision: '42', + // client_name: 'node-sdk', + // client_version: enums.CLIENT_VERSION, + // anonymize_ip: false, + // enrich_decisions: true, + // }, + // }; + // var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; + // assert.deepEqual(eventDispatcherCall[0], expectedObj); + // }); - sinon.assert.calledOnce(eventDispatcher.dispatchEvent); + // it('should flush the queue when the flushInterval occurs', function() { + // var timestamp = new Date().getTime(); + // fakeDecisionResponse = { + // result: '111129', + // reasons: [], + // }; + // bucketStub.returns(fakeDecisionResponse); + // var activate = optlyInstance.activate('testExperiment', 'testUser'); + // assert.strictEqual(activate, 'variation'); + + // optlyInstance.track('testEvent', 'testUser'); + + // sinon.assert.notCalled(eventDispatcher.dispatchEvent); + + // clock.tick(100); + + // sinon.assert.calledOnce(eventDispatcher.dispatchEvent); + + // var expectedObj = { + // url: 'https://logx.optimizely.com/v1/events', + // httpVerb: 'POST', + // params: { + // account_id: '12001', + // project_id: '111001', + // visitors: [ + // { + // snapshots: [ + // { + // decisions: [ + // { + // campaign_id: '4', + // experiment_id: '111127', + // variation_id: '111129', + // metadata: { + // flag_key: '', + // rule_key: 'testExperiment', + // rule_type: 'experiment', + // variation_key: 'variation', + // enabled: true, + // }, + // }, + // ], + // events: [ + // { + // entity_id: '4', + // timestamp: timestamp, + // key: 'campaign_activated', + // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + // }, + // ], + // }, + // ], + // visitor_id: 'testUser', + // attributes: [], + // }, + // { + // attributes: [], + // snapshots: [ + // { + // events: [ + // { + // entity_id: '111095', + // key: 'testEvent', + // timestamp: timestamp, + // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + // }, + // ], + // }, + // ], + // visitor_id: 'testUser', + // }, + // ], + // revision: '42', + // client_name: 'node-sdk', + // client_version: enums.CLIENT_VERSION, + // anonymize_ip: false, + // enrich_decisions: true, + // }, + // }; + // var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; + // assert.deepEqual(eventDispatcherCall[0], expectedObj); + // }); - var expectedObj = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: '4', - experiment_id: '111127', - variation_id: '111129', - metadata: { - flag_key: '', - rule_key: 'testExperiment', - rule_type: 'experiment', - variation_key: 'variation', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: '4', - timestamp: Math.round(new Date().getTime()), - key: 'campaign_activated', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - attributes: [], - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: new Date().getTime(), - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: enums.CLIENT_VERSION, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - assert.deepEqual(eventDispatcherCall[0], expectedObj); - }); - }); + // it('should flush the queue when optimizely.close() is called', function() { + // fakeDecisionResponse = { + // result: '111129', + // reasons: [], + // }; + // bucketStub.returns(fakeDecisionResponse); + // var activate = optlyInstance.activate('testExperiment', 'testUser'); + // assert.strictEqual(activate, 'variation'); + + // optlyInstance.track('testEvent', 'testUser'); + + // sinon.assert.notCalled(eventDispatcher.dispatchEvent); + + // optlyInstance.close(); + + // sinon.assert.calledOnce(eventDispatcher.dispatchEvent); + + // var expectedObj = { + // url: 'https://logx.optimizely.com/v1/events', + // httpVerb: 'POST', + // params: { + // account_id: '12001', + // project_id: '111001', + // visitors: [ + // { + // snapshots: [ + // { + // decisions: [ + // { + // campaign_id: '4', + // experiment_id: '111127', + // variation_id: '111129', + // metadata: { + // flag_key: '', + // rule_key: 'testExperiment', + // rule_type: 'experiment', + // variation_key: 'variation', + // enabled: true, + // }, + // }, + // ], + // events: [ + // { + // entity_id: '4', + // timestamp: Math.round(new Date().getTime()), + // key: 'campaign_activated', + // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + // }, + // ], + // }, + // ], + // visitor_id: 'testUser', + // attributes: [], + // }, + // { + // attributes: [], + // snapshots: [ + // { + // events: [ + // { + // entity_id: '111095', + // key: 'testEvent', + // timestamp: new Date().getTime(), + // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + // }, + // ], + // }, + // ], + // visitor_id: 'testUser', + // }, + // ], + // revision: '42', + // client_name: 'node-sdk', + // client_version: enums.CLIENT_VERSION, + // anonymize_ip: false, + // enrich_decisions: true, + // }, + // }; + // var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; + // assert.deepEqual(eventDispatcherCall[0], expectedObj); + // }); + // }); describe('close method', function() { var eventProcessorStopPromise; @@ -9690,13 +9624,16 @@ describe('lib/optimizely', function() { process: sinon.stub(), start: sinon.stub(), stop: sinon.stub(), + onRunning: sinon.stub(), + onTerminated: sinon.stub(), + onDispatch: sinon.stub(), }; }); - describe('when the event processor stop method returns a promise that fulfills', function() { + describe('when the event processor onTerminated method returns a promise that fulfills', function() { beforeEach(function() { eventProcessorStopPromise = Promise.resolve(); - mockEventProcessor.stop.returns(eventProcessorStopPromise); + mockEventProcessor.onTerminated.returns(eventProcessorStopPromise); const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), }); @@ -9729,10 +9666,11 @@ describe('lib/optimizely', function() { }); }); - describe('when the event processor stop method returns a promise that rejects', function() { + describe('when the event processor onTerminated() method returns a promise that rejects', function() { beforeEach(function() { eventProcessorStopPromise = Promise.reject(new Error('Failed to stop')); - mockEventProcessor.stop.returns(eventProcessorStopPromise); + eventProcessorStopPromise.catch(() => {}); + mockEventProcessor.onTerminated.returns(eventProcessorStopPromise); const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), }); @@ -9779,11 +9717,9 @@ describe('lib/optimizely', function() { var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); var eventDispatcher = getMockEventDispatcher(); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher + ); beforeEach(function() { sinon.stub(errorHandler, 'handleError'); @@ -10107,11 +10043,9 @@ describe('lib/optimizely', function() { beforeEach(function() { bucketStub = sinon.stub(bucketer, 'bucket'); eventDispatcherSpy = sinon.spy(() => Promise.resolve({ statusCode: 200 })); - eventProcessor = createEventProcessor({ - dispatcher: { dispatchEvent: eventDispatcherSpy }, - batchSize: 1, - notificationCenter: notificationCenter, - }); + eventProcessor = getForwardingEventProcessor( + { dispatchEvent: eventDispatcherSpy }, + ); const datafile = testData.getTestProjectConfig(); const mockConfigManager = getMockProjectConfigManager(); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index c78154311..f9b29a6b4 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -17,10 +17,9 @@ import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; -import { EventProcessor } from '../event_processor'; +import { EventProcessor } from '../event_processor/eventProcessor'; import { IOdpManager } from '../core/odp/odp_manager'; -import { OdpConfig } from '../core/odp/odp_config'; import { OdpEvent } from '../core/odp/odp_event'; import { OptimizelySegmentOption } from '../core/odp/optimizely_segment_option'; @@ -28,7 +27,6 @@ import { UserAttributes, EventTags, OptimizelyConfig, - OnReadyResult, UserProfileService, Variation, FeatureFlag, @@ -171,12 +169,17 @@ export default class Optimizely implements Client { this.eventProcessor = config.eventProcessor; - const eventProcessorStartedPromise = this.eventProcessor ? this.eventProcessor.start() : + this.eventProcessor?.start(); + const eventProcessorRunningPromise = this.eventProcessor ? this.eventProcessor.onRunning() : Promise.resolve(undefined); + this.eventProcessor?.onDispatch((event) => { + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, event as any); + }); + this.readyPromise = Promise.all([ projectConfigManagerRunningPromise, - eventProcessorStartedPromise, + eventProcessorRunningPromise, config.odpManager ? config.odpManager.onReady() : Promise.resolve(), ]); @@ -1315,7 +1318,9 @@ export default class Optimizely implements Client { this.notificationCenter.clearAllNotificationListeners(); - const eventProcessorStoppedPromise = this.eventProcessor ? this.eventProcessor.stop() : + this.eventProcessor?.stop(); + + const eventProcessorStoppedPromise = this.eventProcessor ? this.eventProcessor.onTerminated() : Promise.resolve(); if (this.disposeOnUpdate) { diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 54d34a953..0d7a66f2a 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -23,7 +23,6 @@ import { NOTIFICATION_TYPES } from '../utils/enums'; import OptimizelyUserContext from './'; import { createLogger } from '../plugins/logger'; -import { createEventProcessor } from '../plugins/event_processor'; import { createNotificationCenter } from '../core/notification_center'; import Optimizely from '../optimizely'; import errorHandler from '../plugins/error_handler'; @@ -32,6 +31,8 @@ import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; +import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; +import * as logger from '../plugins/logger'; const getMockEventDispatcher = () => { const dispatcher = { @@ -40,6 +41,33 @@ const getMockEventDispatcher = () => { return dispatcher; } +const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(datafileObj), + }); + const eventDispatcher = getMockEventDispatcher(); + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + + const notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); + + const optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: mockConfigManager, + errorHandler: errorHandler, + eventProcessor, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: defaultDecideOptions || [], + notificationCenter, + }); + + sinon.stub(notificationCenter, 'sendNotifications'); + + return { optlyInstance, eventProcessor, eventDispatcher, notificationCenter, createdLogger } +} + describe('lib/optimizely_user_context', function() { describe('APIs', function() { var fakeOptimizely; @@ -305,16 +333,26 @@ describe('lib/optimizely_user_context', function() { logToConsole: false, }); var stubLogHandler; + let optlyInstance, notificationCenter, createdLogger, eventDispatcher; + beforeEach(function() { stubLogHandler = { log: sinon.stub(), }; logging.setLogLevel('notset'); logging.setLogHandler(stubLogHandler); + + ({ optlyInstance, notificationCenter, createdLogger, eventDispatcher} = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + })); }); + afterEach(function() { logging.resetLogger(); + eventDispatcher.dispatchEvent.reset(); + notificationCenter.sendNotifications.restore(); }); + it('should return true when client is not ready', function() { fakeOptimizely = { isValidInstance: sinon.stub().returns(false), @@ -358,11 +396,9 @@ describe('lib/optimizely_user_context', function() { var optlyInstance; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); var eventDispatcher = getMockEventDispatcher(); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', @@ -459,6 +495,10 @@ describe('lib/optimizely_user_context', function() { }); it('should return forced decision object when forced decision is set for a flag and dispatch an event', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; var variationKey = '3324490562'; @@ -497,8 +537,8 @@ describe('lib/optimizely_user_context', function() { assert.equal(metadata.variation_key, variationKey); assert.equal(metadata.enabled, true); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 3); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 3); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -534,6 +574,9 @@ describe('lib/optimizely_user_context', function() { }); it('should return forced decision object when forced decision is set for an experiment rule and dispatch an event', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); var attributes = { country: 'US' }; var user = optlyInstance.createUserContext(userId, attributes); var featureKey = 'feature_1'; @@ -578,8 +621,8 @@ describe('lib/optimizely_user_context', function() { assert.equal(metadata.variation_key, 'b'); assert.equal(metadata.enabled, false); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 3); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 3); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -616,6 +659,9 @@ describe('lib/optimizely_user_context', function() { }); it('should return forced decision object when forced decision is set for a delivery rule and dispatch an event', function() { + const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; var variationKey = '3324490633'; @@ -632,17 +678,17 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap[featureKey]).length, 1); assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][ruleKey], { variationKey }); - sinon.assert.called(stubLogHandler.log); - var logMessage = optlyInstance.decisionService.logger.log.args[4]; - assert.strictEqual(logMessage[0], 2); - assert.strictEqual( - logMessage[1], - 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.' - ); - assert.strictEqual(logMessage[2], variationKey); - assert.strictEqual(logMessage[3], featureKey); - assert.strictEqual(logMessage[4], ruleKey); - assert.strictEqual(logMessage[5], userId); + // sinon.assert.called(stubLogHandler.log); + // var logMessage = optlyInstance.decisionService.logger.log.args[4]; + // assert.strictEqual(logMessage[0], 2); + // assert.strictEqual( + // logMessage[1], + // 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.' + // ); + // assert.strictEqual(logMessage[2], variationKey); + // assert.strictEqual(logMessage[3], featureKey); + // assert.strictEqual(logMessage[4], ruleKey); + // assert.strictEqual(logMessage[5], userId); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; @@ -659,8 +705,8 @@ describe('lib/optimizely_user_context', function() { assert.equal(metadata.variation_key, '3324490633'); assert.equal(metadata.enabled, true); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 3); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 3); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -693,11 +739,9 @@ describe('lib/optimizely_user_context', function() { var optlyInstance; var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); var eventDispatcher = getMockEventDispatcher(); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', @@ -802,11 +846,9 @@ describe('lib/optimizely_user_context', function() { }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); var eventDispatcher = getMockEventDispatcher(); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', @@ -852,11 +894,9 @@ describe('lib/optimizely_user_context', function() { }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); var eventDispatcher = getMockEventDispatcher(); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); var optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: getMockProjectConfigManager({ diff --git a/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts b/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts index 3dabf0401..1e8c04577 100644 --- a/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts +++ b/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { EventDispatcher, EventDispatcherResponse } from '../../event_processor'; +import { EventDispatcher, EventDispatcherResponse } from '../../event_processor/eventDispatcher'; export type Event = { url: string; diff --git a/lib/plugins/event_processor/index.ts b/lib/plugins/event_processor/index.ts deleted file mode 100644 index 3fc0c3cad..000000000 --- a/lib/plugins/event_processor/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2020, 2022-2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../event_processor'; - -export function createEventProcessor( - ...args: ConstructorParameters<typeof LogTierV1EventProcessor> -): LogTierV1EventProcessor { - return new LogTierV1EventProcessor(...args); -} - -export default { createEventProcessor, LocalStoragePendingEventsDispatcher }; diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index 3784fbfd6..585cb0949 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -47,7 +47,6 @@ export class PollingDatafileManager extends BaseService implements DatafileManag private cache?: PersistentKeyValueCache; private sdkKey: string; private datafileAccessToken?: string; - private logger?: LoggerFacade; constructor(config: DatafileManagerConfig) { super(); @@ -80,10 +79,6 @@ export class PollingDatafileManager extends BaseService implements DatafileManag this.datafileUrl = sprintf(urlTemplateToUse, this.sdkKey); } - setLogger(logger: LoggerFacade): void { - this.logger = logger; - } - onUpdate(listener: Consumer<string>): Fn { return this.emitter.on('update', listener); } diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index c03ee9b4c..94c83902b 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -53,7 +53,6 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf public jsonSchemaValidator?: Transformer<unknown, boolean>; public datafileManager?: DatafileManager; private eventEmitter: EventEmitter<{ update: ProjectConfig }> = new EventEmitter(); - private logger?: LoggerFacade; constructor(config: ProjectConfigManagerConfig) { super(); @@ -63,10 +62,6 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf this.datafileManager = config.datafileManager; } - setLogger(logger: LoggerFacade): void { - this.logger = logger; - } - start(): void { if (!this.isNew()) { return; diff --git a/lib/service.spec.ts b/lib/service.spec.ts index 1faae69ac..12df4feff 100644 --- a/lib/service.spec.ts +++ b/lib/service.spec.ts @@ -15,14 +15,16 @@ */ import { it, expect } from 'vitest'; -import { BaseService, ServiceState } from './service'; - +import { BaseService, ServiceState, StartupLog } from './service'; +import { LogLevel } from './modules/logging'; +import { getMockLogger } from './tests/mock/mock_logger'; class TestService extends BaseService { - constructor() { - super(); + constructor(startUpLogs?: StartupLog[]) { + super(startUpLogs); } start(): void { + super.start(); this.setState(ServiceState.Running); this.startPromise.resolve(); } @@ -64,6 +66,30 @@ it('should return correct state when getState() is called', () => { expect(service.getState()).toBe(ServiceState.Failed); }); +it('should log startupLogs on start', () => { + const startUpLogs: StartupLog[] = [ + { + level: LogLevel.WARNING, + message: 'warn message', + params: [1, 2] + }, + { + level: LogLevel.ERROR, + message: 'error message', + params: [3, 4] + }, + ]; + + const logger = getMockLogger(); + const service = new TestService(startUpLogs); + service.setLogger(logger); + service.start(); + + expect(logger.log).toHaveBeenCalledTimes(2); + expect(logger.log).toHaveBeenNthCalledWith(1, LogLevel.WARNING, 'warn message', 1, 2); + expect(logger.log).toHaveBeenNthCalledWith(2, LogLevel.ERROR, 'error message', 3, 4); +}); + it('should return an appropraite promise when onRunning() is called', () => { const service1 = new TestService(); const onRunning1 = service1.onRunning(); diff --git a/lib/service.ts b/lib/service.ts index 48ad8fbff..459488027 100644 --- a/lib/service.ts +++ b/lib/service.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { LoggerFacade, LogLevel } from "./modules/logging"; import { resolvablePromise, ResolvablePromise } from "./utils/promise/resolvablePromise"; @@ -32,6 +33,12 @@ export enum ServiceState { Failed, } +export type StartupLog = { + level: LogLevel; + message: string; + params: any[]; +} + export interface Service { getState(): ServiceState; start(): void; @@ -50,17 +57,30 @@ export abstract class BaseService implements Service { protected state: ServiceState; protected startPromise: ResolvablePromise<void>; protected stopPromise: ResolvablePromise<void>; + protected logger?: LoggerFacade; + protected startupLogs: StartupLog[]; - constructor() { + constructor(startupLogs: StartupLog[] = []) { this.state = ServiceState.New; this.startPromise = resolvablePromise(); this.stopPromise = resolvablePromise(); + this.startupLogs = startupLogs; // avoid unhandled promise rejection this.startPromise.promise.catch(() => {}); this.stopPromise.promise.catch(() => {}); } + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + protected printStartupLogs(): void { + this.startupLogs.forEach(({ level, message, params }) => { + this.logger?.log(level, message, ...params); + }); + } + onRunning(): Promise<void> { return this.startPromise.promise; } @@ -77,6 +97,10 @@ export abstract class BaseService implements Service { return this.state === ServiceState.Starting; } + isRunning(): boolean { + return this.state === ServiceState.Running; + } + isNew(): boolean { return this.state === ServiceState.New; } @@ -89,6 +113,9 @@ export abstract class BaseService implements Service { ].includes(this.state); } - abstract start(): void; + start(): void { + this.printStartupLogs(); + } + abstract stop(): void; } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 8902820eb..f27657378 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -20,7 +20,6 @@ */ import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; -import { EventProcessor, EventDispatcher } from './event_processor'; import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; import { NOTIFICATION_TYPES } from './utils/enums'; @@ -39,9 +38,11 @@ import { IUserAgentParser } from './core/odp/user_agent_parser'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; import { ProjectConfig } from './project_config/project_config'; import { ProjectConfigManager } from './project_config/project_config_manager'; +import { EventDispatcher } from './event_processor/eventDispatcher'; +import { EventProcessor } from './event_processor/eventProcessor'; -export { EventDispatcher, EventProcessor } from './event_processor'; - +export { EventDispatcher } from './event_processor/eventDispatcher'; +export { EventProcessor } from './event_processor/eventProcessor'; export interface BucketerParams { experimentId: string; experimentKey: string; diff --git a/lib/tests/mock/create_event.ts b/lib/tests/mock/create_event.ts new file mode 100644 index 000000000..ec5dd9949 --- /dev/null +++ b/lib/tests/mock/create_event.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function createImpressionEvent(id = 'uuid'): any { + return { + type: 'impression' as const, + timestamp: 69, + uuid: id, + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: '1', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: 'layerId', + }, + + experiment: { + id: 'expId', + key: 'expKey', + }, + + variation: { + id: 'varId', + key: 'varKey', + }, + + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true, + } +} \ No newline at end of file diff --git a/lib/tests/mock/mock_cache.ts b/lib/tests/mock/mock_cache.ts new file mode 100644 index 000000000..5a542deae --- /dev/null +++ b/lib/tests/mock/mock_cache.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SyncCache, AsyncCache } from "../../utils/cache/cache"; +import { Maybe } from "../../utils/type"; + +type SyncCacheWithAddOn<T> = SyncCache<T> & { + size(): number; + getAll(): Map<string, T>; +}; + +type AsyncCacheWithAddOn<T> = AsyncCache<T> & { + size(): Promise<number>; + getAll(): Promise<Map<string, T>>; +}; + +export const getMockSyncCache = <T>(): SyncCacheWithAddOn<T> => { + const cache = { + operation: 'sync' as const, + data: new Map<string, T>(), + remove(key: string): void { + this.data.delete(key); + }, + clear(): void { + this.data.clear(); + }, + getKeys(): string[] { + return Array.from(this.data.keys()); + }, + getAll(): Map<string, T> { + return this.data; + }, + getBatched(keys: string[]): Maybe<T>[] { + return keys.map((key) => this.get(key)); + }, + size(): number { + return this.data.size; + }, + get(key: string): T | undefined { + return this.data.get(key); + }, + set(key: string, value: T): void { + this.data.set(key, value); + } + } + + return cache; +}; + + +export const getMockAsyncCache = <T>(): AsyncCacheWithAddOn<T> => { + const cache = { + operation: 'async' as const, + data: new Map<string, T>(), + async remove(key: string): Promise<void> { + this.data.delete(key); + }, + async clear(): Promise<void> { + this.data.clear(); + }, + async getKeys(): Promise<string[]> { + return Array.from(this.data.keys()); + }, + async getAll(): Promise<Map<string, T>> { + return this.data; + }, + async getBatched(keys: string[]): Promise<Maybe<T>[]> { + return Promise.all(keys.map((key) => this.get(key))); + }, + async size(): Promise<number> { + return this.data.size; + }, + async get(key: string): Promise<Maybe<T>> { + return this.data.get(key); + }, + async set(key: string, value: T): Promise<void> { + this.data.set(key, value); + } + } + + return cache; +}; diff --git a/lib/utils/cache/async_storage_cache.react_native.spec.ts b/lib/utils/cache/async_storage_cache.react_native.spec.ts new file mode 100644 index 000000000..d1a7954e4 --- /dev/null +++ b/lib/utils/cache/async_storage_cache.react_native.spec.ts @@ -0,0 +1,113 @@ + +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('@react-native-async-storage/async-storage', () => { + const MockAsyncStorage = { + data: new Map<string, any>(), + async setItem(key: string, value: string) { + this.data.set(key, value); + }, + async getItem(key: string) { + return this.data.get(key) || null; + }, + async removeItem(key: string) { + this.data.delete(key); + }, + async getAllKeys() { + return Array.from(this.data.keys()); + }, + async clear() { + this.data.clear(); + }, + async multiGet(keys: string[]) { + return keys.map(key => [key, this.data.get(key)]); + }, + } + return { default: MockAsyncStorage }; +}); + +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { AsyncStorageCache } from './async_storage_cache.react_native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +type TestData = { + a: number; + b: string; + d: { e: boolean }; +} + + +describe('AsyncStorageCache', () => { + beforeEach(async () => { + await AsyncStorage.clear(); + }); + + it('should store a stringified value in asyncstorage', async () => { + const cache = new AsyncStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; + await cache.set('key', data); + expect(await AsyncStorage.getItem('key')).toBe(JSON.stringify(data)); + }); + + it('should return undefined if get is called for a nonexistent key', async () => { + const cache = new AsyncStorageCache<string>(); + expect(await cache.get('nonexistent')).toBeUndefined(); + }); + + it('should return the value if get is called for an existing key', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key', 'value'); + expect(await cache.get('key')).toBe('value'); + }); + + it('should return the value after json parsing if get is called for an existing key', async () => { + const cache = new AsyncStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; + await cache.set('key', data); + expect(await cache.get('key')).toEqual(data); + }); + + it('should remove the key from async storage when remove is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key', 'value'); + await cache.remove('key'); + expect(await AsyncStorage.getItem('key')).toBeNull(); + }); + + it('should remove all keys from async storage when clear is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + expect((await AsyncStorage.getAllKeys()).length).toBe(2); + cache.clear(); + expect((await AsyncStorage.getAllKeys()).length).toBe(0); + }); + + it('should return all keys when getKeys is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + expect(await cache.getKeys()).toEqual(['key1', 'key2']); + }); + + it('should return an array of values for an array of keys when getBatched is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + expect(await cache.getBatched(['key1', 'key2'])).toEqual(['value1', 'value2']); + }); +}); diff --git a/lib/utils/cache/async_storage_cache.react_native.ts b/lib/utils/cache/async_storage_cache.react_native.ts new file mode 100644 index 000000000..529287a6c --- /dev/null +++ b/lib/utils/cache/async_storage_cache.react_native.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Maybe } from "../type"; +import { AsyncCache } from "./cache"; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +export class AsyncStorageCache<V> implements AsyncCache<V> { + public readonly operation = 'async'; + + async get(key: string): Promise<V | undefined> { + const value = await AsyncStorage.getItem(key); + return value ? JSON.parse(value) : undefined; + } + + async remove(key: string): Promise<unknown> { + return AsyncStorage.removeItem(key); + } + + async set(key: string, val: V): Promise<unknown> { + return AsyncStorage.setItem(key, JSON.stringify(val)); + } + + async clear(): Promise<unknown> { + return AsyncStorage.clear(); + } + + async getKeys(): Promise<string[]> { + return [... await AsyncStorage.getAllKeys()]; + } + + async getBatched(keys: string[]): Promise<Maybe<V>[]> { + const items = await AsyncStorage.multiGet(keys); + return items.map(([key, value]) => value ? JSON.parse(value) : undefined); + } +} diff --git a/lib/utils/cache/cache.spec.ts b/lib/utils/cache/cache.spec.ts new file mode 100644 index 000000000..150fe4884 --- /dev/null +++ b/lib/utils/cache/cache.spec.ts @@ -0,0 +1,351 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import { SyncPrefixCache, AsyncPrefixCache } from './cache'; +import { getMockSyncCache, getMockAsyncCache } from '../../tests/mock/mock_cache'; + +describe('SyncPrefixCache', () => { + describe('set', () => { + it('should add prefix to key when setting in the underlying cache', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + prefixCache.set('key', 'value'); + expect(cache.get('prefix:key')).toEqual('value'); + }); + + it('should transform value when setting in the underlying cache', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + prefixCache.set('key', 'value'); + expect(cache.get('prefix:key')).toEqual('VALUE'); + }); + + it('should work correctly with empty prefix', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + prefixCache.set('key', 'value'); + expect(cache.get('key')).toEqual('VALUE'); + }); + }); + + describe('get', () => { + it('should remove prefix from key when getting from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + cache.set('prefix:key', 'value'); + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + expect(prefixCache.get('key')).toEqual('value'); + }); + + it('should transform value after getting from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + cache.set('prefix:key', 'VALUE'); + expect(prefixCache.get('key')).toEqual('value'); + }); + + + it('should work correctly with empty prefix', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + cache.set('key', 'VALUE'); + expect(prefixCache.get('key')).toEqual('value'); + }); + }); + + describe('remove', () => { + it('should remove the correct value from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + cache.set('prefix:key', 'value'); + cache.set('key', 'value'); + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + prefixCache.remove('key'); + expect(cache.get('prefix:key')).toBeUndefined(); + expect(cache.get('key')).toEqual('value'); + }); + + it('should work with empty prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key', 'value'); + const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); + prefixCache.remove('key'); + expect(cache.get('key')).toBeUndefined(); + }); + }); + + describe('clear', () => { + it('should remove keys with correct prefix from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + cache.set('prefix:key1', 'value1'); + cache.set('prefix:key2', 'value2'); + + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + prefixCache.clear(); + + expect(cache.get('key1')).toEqual('value1'); + expect(cache.get('key2')).toEqual('value2'); + expect(cache.get('prefix:key1')).toBeUndefined(); + expect(cache.get('prefix:key2')).toBeUndefined(); + }); + + it('should work with empty prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + + const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); + prefixCache.clear(); + + expect(cache.get('key1')).toBeUndefined(); + expect(cache.get('key2')).toBeUndefined(); + }); + }); + + describe('getKeys', () => { + it('should return keys with correct prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + cache.set('prefix:key3', 'value1'); + cache.set('prefix:key4', 'value2'); + + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + + const keys = prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key3', 'key4'])); + }); + + it('should work with empty prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + + const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); + + const keys = prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key1', 'key2'])); + }); + }); + + describe('getBatched', () => { + it('should return values with correct prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + cache.set('key3', 'value3'); + cache.set('prefix:key1', 'prefix:value1'); + cache.set('prefix:key2', 'prefix:value2'); + + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + + const values = prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should transform values after getting from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'VALUE1'); + cache.set('key2', 'VALUE2'); + cache.set('key3', 'VALUE3'); + cache.set('prefix:key1', 'PREFIX:VALUE1'); + cache.set('prefix:key2', 'PREFIX:VALUE2'); + + const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); + + const values = prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should work with empty prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + + const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); + + const values = prefixCache.getBatched(['key1', 'key2']); + expect(values).toEqual(expect.arrayContaining(['value1', 'value2'])); + }); + }); +}); + +describe('AsyncPrefixCache', () => { + describe('set', () => { + it('should add prefix to key when setting in the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + await prefixCache.set('key', 'value'); + expect(await cache.get('prefix:key')).toEqual('value'); + }); + + it('should transform value when setting in the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await prefixCache.set('key', 'value'); + expect(await cache.get('prefix:key')).toEqual('VALUE'); + }); + + it('should work correctly with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await prefixCache.set('key', 'value'); + expect(await cache.get('key')).toEqual('VALUE'); + }); + }); + + describe('get', () => { + it('should remove prefix from key when getting from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('prefix:key', 'value'); + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + expect(await prefixCache.get('key')).toEqual('value'); + }); + + it('should transform value after getting from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await cache.set('prefix:key', 'VALUE'); + expect(await prefixCache.get('key')).toEqual('value'); + }); + + + it('should work correctly with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await cache.set('key', 'VALUE'); + expect(await prefixCache.get('key')).toEqual('value'); + }); + }); + + describe('remove', () => { + it('should remove the correct value from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + cache.set('prefix:key', 'value'); + cache.set('key', 'value'); + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + await prefixCache.remove('key'); + expect(await cache.get('prefix:key')).toBeUndefined(); + expect(await cache.get('key')).toEqual('value'); + }); + + it('should work with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key', 'value'); + const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); + await prefixCache.remove('key'); + expect(await cache.get('key')).toBeUndefined(); + }); + }); + + describe('clear', () => { + it('should remove keys with correct prefix from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + await cache.set('prefix:key1', 'value1'); + await cache.set('prefix:key2', 'value2'); + + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + await prefixCache.clear(); + + expect(await cache.get('key1')).toEqual('value1'); + expect(await cache.get('key2')).toEqual('value2'); + expect(await cache.get('prefix:key1')).toBeUndefined(); + expect(await cache.get('prefix:key2')).toBeUndefined(); + }); + + it('should work with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); + await prefixCache.clear(); + + expect(await cache.get('key1')).toBeUndefined(); + expect(await cache.get('key2')).toBeUndefined(); + }); + }); + + describe('getKeys', () => { + it('should return keys with correct prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + await cache.set('prefix:key3', 'value1'); + await cache.set('prefix:key4', 'value2'); + + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + + const keys = await prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key3', 'key4'])); + }); + + it('should work with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); + + const keys = await prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key1', 'key2'])); + }); + }); + + describe('getBatched', () => { + it('should return values with correct prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + await cache.set('key3', 'value3'); + await cache.set('prefix:key1', 'prefix:value1'); + await cache.set('prefix:key2', 'prefix:value2'); + + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + + const values = await prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should transform values after getting from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'VALUE1'); + await cache.set('key2', 'VALUE2'); + await cache.set('key3', 'VALUE3'); + await cache.set('prefix:key1', 'PREFIX:VALUE1'); + await cache.set('prefix:key2', 'PREFIX:VALUE2'); + + const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); + + const values = await prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should work with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); + + const values = await prefixCache.getBatched(['key1', 'key2']); + expect(values).toEqual(expect.arrayContaining(['value1', 'value2'])); + }); + }); +}); \ No newline at end of file diff --git a/lib/utils/cache/cache.ts b/lib/utils/cache/cache.ts new file mode 100644 index 000000000..46dcebbda --- /dev/null +++ b/lib/utils/cache/cache.ts @@ -0,0 +1,154 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transformer } from '../../utils/type'; +import { Maybe } from '../../utils/type'; + +export type CacheOp = 'sync' | 'async'; +export type OpValue<Op extends CacheOp, V> = Op extends 'sync' ? V : Promise<V>; + +export interface CacheWithOp<Op extends CacheOp, V> { + operation: Op; + set(key: string, value: V): OpValue<Op, unknown>; + get(key: string): OpValue<Op, Maybe<V>>; + remove(key: string): OpValue<Op, unknown>; + clear(): OpValue<Op, unknown>; + getKeys(): OpValue<Op, string[]>; + getBatched(keys: string[]): OpValue<Op, Maybe<V>[]>; +} + +export type SyncCache<V> = CacheWithOp<'sync', V>; +export type AsyncCache<V> = CacheWithOp<'async', V>; +export type Cache<V> = SyncCache<V> | AsyncCache<V>; + +export class SyncPrefixCache<U, V> implements SyncCache<V> { + private cache: SyncCache<U>; + private prefix: string; + private transformGet: Transformer<U, V>; + private transformSet: Transformer<V, U>; + + public readonly operation = 'sync'; + + constructor( + cache: SyncCache<U>, + prefix: string, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U> + ) { + this.cache = cache; + this.prefix = prefix; + this.transformGet = transformGet; + this.transformSet = transformSet; + } + + private addPrefix(key: string): string { + return `${this.prefix}${key}`; + } + + private removePrefix(key: string): string { + return key.substring(this.prefix.length); + } + + set(key: string, value: V): unknown { + return this.cache.set(this.addPrefix(key), this.transformSet(value)); + } + + get(key: string): V | undefined { + const value = this.cache.get(this.addPrefix(key)); + return value ? this.transformGet(value) : undefined; + } + + remove(key: string): unknown { + return this.cache.remove(this.addPrefix(key)); + } + + clear(): void { + this.getInternalKeys().forEach((key) => this.cache.remove(key)); + } + + private getInternalKeys(): string[] { + return this.cache.getKeys().filter((key) => key.startsWith(this.prefix)); + } + + getKeys(): string[] { + return this.getInternalKeys().map((key) => this.removePrefix(key)); + } + + getBatched(keys: string[]): Maybe<V>[] { + return this.cache.getBatched(keys.map((key) => this.addPrefix(key))) + .map((value) => value ? this.transformGet(value) : undefined); + } +} + +export class AsyncPrefixCache<U, V> implements AsyncCache<V> { + private cache: AsyncCache<U>; + private prefix: string; + private transformGet: Transformer<U, V>; + private transformSet: Transformer<V, U>; + + public readonly operation = 'async'; + + constructor( + cache: AsyncCache<U>, + prefix: string, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U> + ) { + this.cache = cache; + this.prefix = prefix; + this.transformGet = transformGet; + this.transformSet = transformSet; + } + + private addPrefix(key: string): string { + return `${this.prefix}${key}`; + } + + private removePrefix(key: string): string { + return key.substring(this.prefix.length); + } + + set(key: string, value: V): Promise<unknown> { + return this.cache.set(this.addPrefix(key), this.transformSet(value)); + } + + async get(key: string): Promise<V | undefined> { + const value = await this.cache.get(this.addPrefix(key)); + return value ? this.transformGet(value) : undefined; + } + + remove(key: string): Promise<unknown> { + return this.cache.remove(this.addPrefix(key)); + } + + async clear(): Promise<void> { + const keys = await this.getInternalKeys(); + await Promise.all(keys.map((key) => this.cache.remove(key))); + } + + private async getInternalKeys(): Promise<string[]> { + return this.cache.getKeys().then((keys) => keys.filter((key) => key.startsWith(this.prefix))); + } + + async getKeys(): Promise<string[]> { + return this.getInternalKeys().then((keys) => keys.map((key) => this.removePrefix(key))); + } + + async getBatched(keys: string[]): Promise<Maybe<V>[]> { + const values = await this.cache.getBatched(keys.map((key) => this.addPrefix(key))); + return values.map((value) => value ? this.transformGet(value) : undefined); + } +} diff --git a/lib/utils/cache/local_storage_cache.browser.spec.ts b/lib/utils/cache/local_storage_cache.browser.spec.ts new file mode 100644 index 000000000..37e0735ba --- /dev/null +++ b/lib/utils/cache/local_storage_cache.browser.spec.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; +import { LocalStorageCache } from './local_storage_cache.browser'; + +type TestData = { + a: number; + b: string; + d: { e: boolean }; +} + +describe('LocalStorageCache', () => { + beforeEach(() => { + localStorage.clear(); + }); + + it('should store a stringified value in local storage', () => { + const cache = new LocalStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; + cache.set('key', data); + expect(localStorage.getItem('key')).toBe(JSON.stringify(data)); + }); + + it('should return undefined if get is called for a nonexistent key', () => { + const cache = new LocalStorageCache<string>(); + expect(cache.get('nonexistent')).toBeUndefined(); + }); + + it('should return the value if get is called for an existing key', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key', 'value'); + expect(cache.get('key')).toBe('value'); + }); + + it('should return the value after json parsing if get is called for an existing key', () => { + const cache = new LocalStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; + cache.set('key', data); + expect(cache.get('key')).toEqual(data); + }); + + it('should remove the key from local storage when remove is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key', 'value'); + cache.remove('key'); + expect(localStorage.getItem('key')).toBeNull(); + }); + + it('should remove all keys from local storage when clear is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + expect(localStorage.length).toBe(2); + cache.clear(); + expect(localStorage.length).toBe(0); + }); + + it('should return all keys when getKeys is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + expect(cache.getKeys()).toEqual(['key1', 'key2']); + }); + + it('should return an array of values for an array of keys when getBatched is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + expect(cache.getBatched(['key1', 'key2'])).toEqual(['value1', 'value2']); + }); +}); diff --git a/lib/utils/cache/local_storage_cache.browser.ts b/lib/utils/cache/local_storage_cache.browser.ts new file mode 100644 index 000000000..594b722d2 --- /dev/null +++ b/lib/utils/cache/local_storage_cache.browser.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Maybe } from "../type"; +import { SyncCache } from "./cache"; + +export class LocalStorageCache<V> implements SyncCache<V> { + public readonly operation = 'sync'; + + public set(key: string, value: V): void { + localStorage.setItem(key, JSON.stringify(value)); + } + + public get(key: string): Maybe<V> { + const value = localStorage.getItem(key); + return value ? JSON.parse(value) : undefined; + } + + public remove(key: string): void { + localStorage.removeItem(key); + } + + public clear(): void { + localStorage.clear(); + } + + public getKeys(): string[] { + const keys: string[] = []; + for(let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) { + keys.push(key); + } + } + return keys; + } + + getBatched(keys: string[]): Maybe<V>[] { + return keys.map((k) => this.get(k)); + } +} diff --git a/lib/utils/event_processor_config_validator/index.tests.js b/lib/utils/event_processor_config_validator/index.tests.js deleted file mode 100644 index 6ecc6a134..000000000 --- a/lib/utils/event_processor_config_validator/index.tests.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; - -import eventProcessorConfigValidator from './index'; - -describe('utils/event_processor_config_validator', function() { - describe('validateEventFlushInterval', function() { - it('returns false for null & undefined', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(null)); - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(undefined)); - }); - - it('returns false for a string', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval('not a number')); - }); - - it('returns false for an object', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval({ value: 'not a number' })); - }); - - it('returns false for a negative integer', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(-1000)); - }); - - it('returns false for 0', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(0)); - }); - - it('returns true for a positive integer', function() { - assert.isTrue(eventProcessorConfigValidator.validateEventFlushInterval(30000)); - }); - }); - - describe('validateEventBatchSize', function() { - it('returns false for null & undefined', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(null)); - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(undefined)); - }); - - it('returns false for a string', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize('not a number')); - }); - - it('returns false for an object', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize({ value: 'not a number' })); - }); - - it('returns false for a negative integer', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(-1000)); - }); - - it('returns false for 0', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(0)); - }); - - it('returns true for a positive integer', function() { - assert.isTrue(eventProcessorConfigValidator.validateEventBatchSize(10)); - }); - }); -}); diff --git a/lib/utils/event_processor_config_validator/index.ts b/lib/utils/event_processor_config_validator/index.ts deleted file mode 100644 index e6bd304bb..000000000 --- a/lib/utils/event_processor_config_validator/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import fns from '../fns'; - -/** - * Return true if the argument is a valid event batch size, false otherwise - * @param {unknown} eventBatchSize - * @returns {boolean} - */ -const validateEventBatchSize = function(eventBatchSize: unknown): boolean { - if (typeof eventBatchSize === 'number' && fns.isSafeInteger(eventBatchSize)) { - return eventBatchSize >= 1; - } - return false; -} - -/** - * Return true if the argument is a valid event flush interval, false otherwise - * @param {unknown} eventFlushInterval - * @returns {boolean} - */ -const validateEventFlushInterval = function(eventFlushInterval: unknown): boolean { - if (typeof eventFlushInterval === 'number' && fns.isSafeInteger(eventFlushInterval)) { - return eventFlushInterval > 0; - } - return false; -} - -export default { - validateEventBatchSize: validateEventBatchSize, - validateEventFlushInterval: validateEventFlushInterval, -} diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index aa256ef1b..1be540540 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventTags } from '../../event_processor'; +import { EventTags } from '../../event_processor/events'; import { LoggerFacade } from '../../modules/logging'; import { diff --git a/lib/utils/executor/backoff_retry_runner.spec.ts b/lib/utils/executor/backoff_retry_runner.spec.ts new file mode 100644 index 000000000..6e2674b10 --- /dev/null +++ b/lib/utils/executor/backoff_retry_runner.spec.ts @@ -0,0 +1,139 @@ +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { runWithRetry } from './backoff_retry_runner'; +import { advanceTimersByTime } from '../../../tests/testUtils'; + +const exhaustMicrotasks = async (loop = 100) => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +} + +describe('runWithRetry', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should return the result of the task if it succeeds in first try', async () => { + const task = async () => 1; + const { result } = runWithRetry(task); + expect(await result).toBe(1); + }); + + it('should retry the task if it fails', async () => { + let count = 0; + const task = async () => { + count++; + if (count === 1) { + throw new Error('error'); + } + return 1; + }; + const { result } = runWithRetry(task); + + await exhaustMicrotasks(); + await advanceTimersByTime(0); + + expect(await result).toBe(1); + }); + + it('should retry the task up to the maxRetries before failing', async () => { + let count = 0; + const task = async () => { + count++; + throw new Error('error'); + }; + const { result } = runWithRetry(task, undefined, 5); + + for(let i = 0; i < 5; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + + try { + await result; + } catch (e) { + expect(count).toBe(6); + } + }); + + it('should retry idefinitely if maxRetries is undefined', async () => { + let count = 0; + const task = async () => { + count++; + if (count < 500) { + throw new Error('error'); + } + return 1; + }; + + const { result } = runWithRetry(task); + + for(let i = 0; i < 500; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + expect(await result).toBe(1); + expect(count).toBe(500); + }); + + it('should use the backoff controller to delay retries', async () => { + const task = vi.fn().mockImplementation(async () => { + throw new Error('error'); + }); + + const delays = [7, 13, 19, 20, 27]; + + let backoffCount = 0; + const backoff = { + backoff: () => { + return delays[backoffCount++]; + }, + reset: () => {}, + }; + + const { result } = runWithRetry(task, backoff, 5); + result.catch(() => {}); + + expect(task).toHaveBeenCalledTimes(1); + + for(let i = 1; i <= 5; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(delays[i - 1] - 1); + expect(task).toHaveBeenCalledTimes(i); + await advanceTimersByTime(1); + expect(task).toHaveBeenCalledTimes(i + 1); + } + }); + + it('should cancel the retry if the cancel function is called', async () => { + let count = 0; + const task = async () => { + count++; + throw new Error('error'); + }; + + const { result, cancelRetry } = runWithRetry(task, undefined, 100); + + for(let i = 0; i < 5; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + + cancelRetry(); + + for(let i = 0; i < 100; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + + try { + await result; + } catch (e) { + expect(count).toBe(6); + } + }); +}); diff --git a/lib/utils/executor/backoff_retry_runner.ts b/lib/utils/executor/backoff_retry_runner.ts new file mode 100644 index 000000000..504412c24 --- /dev/null +++ b/lib/utils/executor/backoff_retry_runner.ts @@ -0,0 +1,52 @@ +import { resolvablePromise, ResolvablePromise } from "../promise/resolvablePromise"; +import { BackoffController } from "../repeater/repeater"; +import { AsyncProducer, Fn } from "../type"; + +export type RunResult<T> = { + result: Promise<T>; + cancelRetry: Fn; +}; + +type CancelSignal = { + cancelled: boolean; +} + +const runTask = <T>( + task: AsyncProducer<T>, + returnPromise: ResolvablePromise<T>, + cancelSignal: CancelSignal, + backoff?: BackoffController, + retryRemaining?: number, +): void => { + task().then((res) => { + returnPromise.resolve(res); + }).catch((e) => { + if (retryRemaining === 0) { + returnPromise.reject(e); + return; + } + if (cancelSignal.cancelled) { + returnPromise.reject(new Error('Retry cancelled')); + return; + } + const delay = backoff?.backoff() ?? 0; + setTimeout(() => { + retryRemaining = retryRemaining === undefined ? undefined : retryRemaining - 1; + runTask(task, returnPromise, cancelSignal, backoff, retryRemaining); + }, delay); + }); +} + +export const runWithRetry = <T>( + task: AsyncProducer<T>, + backoff?: BackoffController, + maxRetries?: number +): RunResult<T> => { + const returnPromise = resolvablePromise<T>(); + const cancelSignal = { cancelled: false }; + const cancelRetry = () => { + cancelSignal.cancelled = true; + } + runTask(task, returnPromise, cancelSignal, backoff, maxRetries); + return { cancelRetry, result: returnPromise.promise }; +} diff --git a/lib/utils/http_request_handler/http_util.ts b/lib/utils/http_request_handler/http_util.ts new file mode 100644 index 000000000..c38217a40 --- /dev/null +++ b/lib/utils/http_request_handler/http_util.ts @@ -0,0 +1,4 @@ + +export const isSuccessStatusCode = (statusCode: number): boolean => { + return statusCode >= 200 && statusCode < 400; +} diff --git a/lib/plugins/event_processor/index.react_native.ts b/lib/utils/id_generator/index.ts similarity index 50% rename from lib/plugins/event_processor/index.react_native.ts rename to lib/utils/id_generator/index.ts index 9481987cb..5f3c72387 100644 --- a/lib/plugins/event_processor/index.react_native.ts +++ b/lib/utils/id_generator/index.ts @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,13 +14,18 @@ * limitations under the License. */ -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../event_processor/index.react_native'; +const idSuffixBase = 10_000; -export function createEventProcessor( - ...args: ConstructorParameters<typeof LogTierV1EventProcessor> -): LogTierV1EventProcessor { - return new LogTierV1EventProcessor(...args); -} +export class IdGenerator { + private idSuffixOffset = 0; -export default { createEventProcessor, LocalStoragePendingEventsDispatcher }; - + // getId returns an Id that generally increases with each call. + // only exceptions are when idSuffix rotates back to 0 within the same millisecond + // or when the clock goes back + getId(): string { + const idSuffix = idSuffixBase + this.idSuffixOffset; + this.idSuffixOffset = (this.idSuffixOffset + 1) % idSuffixBase; + const timestamp = Date.now(); + return `${timestamp}${idSuffix}`; + } +} diff --git a/lib/utils/import.react_native/@react-native-community/netinfo.ts b/lib/utils/import.react_native/@react-native-community/netinfo.ts new file mode 100644 index 000000000..434a0a1b3 --- /dev/null +++ b/lib/utils/import.react_native/@react-native-community/netinfo.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { NetInfoSubscription, NetInfoChangeHandler } from '@react-native-community/netinfo'; +import { Maybe } from '../../type'; + +export { NetInfoState } from '@react-native-community/netinfo'; +export type NetInfoAddEventListerType = (listener: NetInfoChangeHandler) => NetInfoSubscription; + +let addEventListener: Maybe<NetInfoAddEventListerType> = undefined; + +const requireNetInfo = () => { + try { + return require('@react-native-community/netinfo'); + } catch (e) { + return undefined; + } +} + +export const isAvailable = (): boolean => requireNetInfo() !== undefined; + +const netinfo = requireNetInfo(); +addEventListener = netinfo?.addEventListener; + +export { addEventListener }; diff --git a/lib/utils/repeater/repeater.spec.ts b/lib/utils/repeater/repeater.spec.ts index cebb17e38..7d998e7b6 100644 --- a/lib/utils/repeater/repeater.spec.ts +++ b/lib/utils/repeater/repeater.spec.ts @@ -16,7 +16,6 @@ import { expect, vi, it, beforeEach, afterEach, describe } from 'vitest'; import { ExponentialBackoff, IntervalRepeater } from './repeater'; import { advanceTimersByTime } from '../../../tests/testUtils'; -import { ad } from 'vitest/dist/chunks/reporters.C_zwCd4j'; import { resolvablePromise } from '../promise/resolvablePromise'; describe("ExponentialBackoff", () => { diff --git a/lib/utils/repeater/repeater.ts b/lib/utils/repeater/repeater.ts index f758f0dc9..1425db431 100644 --- a/lib/utils/repeater/repeater.ts +++ b/lib/utils/repeater/repeater.ts @@ -30,7 +30,7 @@ export interface Repeater { start(immediateExecution?: boolean): void; stop(): void; reset(): void; - setTask(task: AsyncTransformer<number, void>): void; + setTask(task: AsyncTransformer<number, unknown>): void; } export interface BackoffController { diff --git a/lib/utils/type.ts b/lib/utils/type.ts index 9c9a704dc..ddf3871aa 100644 --- a/lib/utils/type.ts +++ b/lib/utils/type.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -export type Fn = () => void; +export type Fn = () => unknown; +export type AsyncFn = () => Promise<unknown>; export type AsyncTransformer<A, B> = (arg: A) => Promise<B>; export type Transformer<A, B> = (arg: A) => B; @@ -23,3 +24,5 @@ export type AsyncComsumer<T> = (arg: T) => Promise<void>; export type Producer<T> = () => T; export type AsyncProducer<T> = () => Promise<T>; + +export type Maybe<T> = T | undefined; diff --git a/tests/eventQueue.spec.ts b/tests/eventQueue.spec.ts deleted file mode 100644 index f794248dd..000000000 --- a/tests/eventQueue.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; - -import { DefaultEventQueue, SingleEventQueue } from '../lib/event_processor/eventQueue' - -describe('eventQueue', () => { - beforeEach(() => { - vi.useFakeTimers() - }) - - afterEach(() => { - vi.useRealTimers() - vi.resetAllMocks() - }) - - describe('SingleEventQueue', () => { - it('should immediately invoke the sink function when items are enqueued', () => { - const sinkFn = vi.fn() - const queue = new SingleEventQueue<number>({ - sink: sinkFn, - }) - - queue.start() - - queue.enqueue(1) - - expect(sinkFn).toBeCalledTimes(1) - expect(sinkFn).toHaveBeenLastCalledWith([1]) - - queue.enqueue(2) - expect(sinkFn).toBeCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - }) - }) - - describe('DefaultEventQueue', () => { - it('should treat maxQueueSize = -1 as 1', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: -1, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should treat maxQueueSize = 0 as 1', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 0, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should invoke the sink function when maxQueueSize is reached', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - queue.enqueue(3) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2, 3]) - - queue.enqueue(4) - queue.enqueue(5) - queue.enqueue(6) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([4, 5, 6]) - - queue.stop() - }) - - it('should invoke the sink function when the interval has expired', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - vi.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2]) - - queue.enqueue(3) - vi.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([3]) - - queue.stop() - }) - - it('should invoke the sink function when an item incompatable with the current batch (according to batchComparator) is received', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<string>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - // This batchComparator returns true when the argument strings start with the same letter - batchComparator: (s1, s2) => s1[0] === s2[0] - }) - - queue.start() - - queue.enqueue('a1') - queue.enqueue('a2') - // After enqueuing these strings, both starting with 'a', the sinkFn should not yet be called. Thus far all the items enqueued are - // compatible according to the batchComparator. - expect(sinkFn).not.toHaveBeenCalled() - - // Enqueuing a string starting with 'b' should cause the sinkFn to be called - queue.enqueue('b1') - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith(['a1', 'a2']) - }) - - it('stop() should flush the existing queue and call timer.stop()', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - vi.spyOn(queue.timer, 'stop') - - queue.start() - queue.enqueue(1) - - // stop + start is called when the first item is enqueued - expect(queue.timer.stop).toHaveBeenCalledTimes(1) - - queue.stop() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.stop).toHaveBeenCalledTimes(2) - }) - - it('flush() should clear the current batch', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - vi.spyOn(queue.timer, 'refresh') - - queue.start() - queue.enqueue(1) - queue.flush() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.refresh).toBeCalledTimes(1) - - queue.stop() - }) - - it('stop() should return a promise', () => { - const promise = Promise.resolve() - const sinkFn = vi.fn().mockReturnValue(promise) - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - expect(queue.stop()).toBe(promise) - }) - - it('should start the timer when the first event is put into the queue', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - vi.advanceTimersByTime(99) - queue.enqueue(1) - - vi.advanceTimersByTime(2) - expect(sinkFn).toHaveBeenCalledTimes(0) - vi.advanceTimersByTime(98) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - - vi.advanceTimersByTime(500) - // ensure sink function wasnt called again since no events have - // been added - expect(sinkFn).toHaveBeenCalledTimes(1) - - queue.enqueue(2) - - vi.advanceTimersByTime(100) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - - }) - - it('should not enqueue additional events after stop() is called', () => { - const sinkFn = vi.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 30000, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - queue.start() - queue.enqueue(1) - queue.stop() - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - sinkFn.mockClear() - queue.enqueue(2) - queue.enqueue(3) - queue.enqueue(4) - expect(sinkFn).toBeCalledTimes(0) - }) - }) -}) diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts index 6f076e614..a5fab6aff 100644 --- a/tests/index.react_native.spec.ts +++ b/tests/index.react_native.spec.ts @@ -16,14 +16,12 @@ import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import * as logging from '../lib/modules/logging/logger'; -import * as eventProcessor from '../lib//plugins/event_processor/index.react_native'; import Optimizely from '../lib/optimizely'; import testData from '../lib/tests/test_data'; import packageJSON from '../package.json'; import optimizelyFactory from '../lib/index.react_native'; import configValidator from '../lib/utils/config_validator'; -import eventProcessorConfigValidator from '../lib/utils/event_processor_config_validator'; import { getMockProjectConfigManager } from '../lib/tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../lib/project_config/project_config'; diff --git a/tests/pendingEventsDispatcher.spec.ts b/tests/pendingEventsDispatcher.spec.ts deleted file mode 100644 index d39b58e22..000000000 --- a/tests/pendingEventsDispatcher.spec.ts +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, it, expect, vi, MockInstance } from 'vitest'; - -vi.mock('../lib/utils/fns', async (importOriginal) => { - const actual: any = await importOriginal(); - return { - __esModule: true, - uuid: vi.fn(), - getTimestamp: vi.fn(), - objectValues: actual.objectValues, - } -}); - -import { - LocalStoragePendingEventsDispatcher, - PendingEventsDispatcher, - DispatcherEntry, -} from '../lib/event_processor/pendingEventsDispatcher' -import { EventDispatcher, EventDispatcherResponse, EventV1Request } from '../lib/event_processor/eventDispatcher' -import { EventV1 } from '../lib/event_processor/v1/buildEventV1' -import { PendingEventsStore, LocalStorageStore } from '../lib/event_processor/pendingEventsStore' -import { uuid, getTimestamp } from '../lib/utils/fns' -import { resolvablePromise, ResolvablePromise } from '../lib/utils/promise/resolvablePromise'; - -describe('LocalStoragePendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - let eventDispatcherResponses: Array<ResolvablePromise<EventDispatcherResponse>> - - beforeEach(() => { - eventDispatcherResponses = []; - originalEventDispatcher = { - dispatchEvent: vi.fn().mockImplementation(() => { - const response = resolvablePromise<EventDispatcherResponse>() - eventDispatcherResponses.push(response) - return response.promise - }), - } - - pendingEventsDispatcher = new LocalStoragePendingEventsDispatcher({ - eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as MockInstance).mockReturnValue(1) - ;((uuid as unknown) as MockInstance).mockReturnValue('uuid') - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', async () => { - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request) - - eventDispatcherResponses[0].resolve({ statusCode: 200 }) - - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) - .mock.calls[0] - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as MockInstance).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => { - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request) - - eventDispatcherResponses[0].resolve({ statusCode: 400 }) - - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) - .mock.calls[0] - - eventDispatcherResponses[0].resolve({ statusCode: 400 }) - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as MockInstance).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - }) -}) - -describe('PendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - let store: PendingEventsStore<DispatcherEntry> - let eventDispatcherResponses: Array<ResolvablePromise<EventDispatcherResponse>> - - beforeEach(() => { - eventDispatcherResponses = []; - - originalEventDispatcher = { - dispatchEvent: vi.fn().mockImplementation(() => { - const response = resolvablePromise<EventDispatcherResponse>() - eventDispatcherResponses.push(response) - return response.promise - }), - } - - store = new LocalStorageStore({ - key: 'test', - maxValues: 3, - }) - pendingEventsDispatcher = new PendingEventsDispatcher({ - store, - eventDispatcher: originalEventDispatcher, - }); - ((getTimestamp as unknown) as MockInstance).mockReturnValue(1); - ((uuid as unknown) as MockInstance).mockReturnValue('uuid'); - }) - - afterEach(() => { - localStorage.clear() - }) - - describe('dispatch', () => { - describe('when the dispatch is successful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', async () => { - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - - eventDispatcherResponses[0].resolve({ statusCode: 200 }) - await eventDispatcherResponses[0].promise - - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) - .mock.calls[0] - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as MockInstance, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - expect(store.values()).toHaveLength(0) - }) - }) - - describe('when the dispatch is unsuccessful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', async () => { - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - - eventDispatcherResponses[0].resolve({ statusCode: 400 }) - await eventDispatcherResponses[0].promise - - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as MockInstance) - .mock.calls[0] - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as MockInstance, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - expect(store.values()).toHaveLength(0) - }) - }) - }) - - describe('sendPendingEvents', () => { - describe('when no pending events are in the store', () => { - it('should not invoked dispatch', () => { - expect(store.values()).toHaveLength(0) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).not.toHaveBeenCalled() - }) - }) - - describe('when there are multiple pending events in the store', () => { - it('should dispatch all of the pending events, and remove them from store', async () => { - expect(store.values()).toHaveLength(0) - - const eventV1Request1: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event1' } as unknown) as EventV1, - } - - const eventV1Request2: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event2' } as unknown) as EventV1, - } - - store.set('uuid1', { - uuid: 'uuid1', - timestamp: 1, - request: eventV1Request1, - }) - store.set('uuid2', { - uuid: 'uuid2', - timestamp: 2, - request: eventV1Request2, - }) - - expect(store.values()).toHaveLength(2) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) - - eventDispatcherResponses[0].resolve({ statusCode: 200 }) - eventDispatcherResponses[1].resolve({ statusCode: 200 }) - await Promise.all([eventDispatcherResponses[0].promise, eventDispatcherResponses[1].promise]) - expect(store.values()).toHaveLength(0) - }) - }) - }) -}) diff --git a/tests/pendingEventsStore.spec.ts b/tests/pendingEventsStore.spec.ts deleted file mode 100644 index 9c255b118..000000000 --- a/tests/pendingEventsStore.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, it, expect, vi, MockInstance } from 'vitest'; - -import { LocalStorageStore } from '../lib/event_processor/pendingEventsStore' - -type TestEntry = { - uuid: string - timestamp: number - value: string -} - -describe('LocalStorageStore', () => { - let store: LocalStorageStore<TestEntry> - beforeEach(() => { - store = new LocalStorageStore({ - key: 'test_key', - maxValues: 3, - }) - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should get, set and remove items', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('1', { - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.values()).toHaveLength(1) - - store.remove('1') - - expect(store.values()).toHaveLength(0) - }) - - it('should allow replacement of the entire map', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'first' }, - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - ]) - - const newMap: { [key: string]: TestEntry } = {} - store.values().forEach(item => { - newMap[item.uuid] = { - ...item, - value: 'new', - } - }) - store.replace(newMap) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'new' }, - { uuid: '2', timestamp: 2, value: 'new' }, - { uuid: '3', timestamp: 3, value: 'new' }, - ]) - }) - - it(`shouldn't allow more than the configured maxValues, using timestamp to remove the oldest entries`, () => { - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('4', { - uuid: '4', - timestamp: 4, - value: 'fourth', - }) - - expect(store.values()).toEqual([ - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - { uuid: '4', timestamp: 4, value: 'fourth' }, - ]) - }) -}) diff --git a/tests/reactNativeEventsStore.spec.ts b/tests/reactNativeEventsStore.spec.ts deleted file mode 100644 index d7155a629..000000000 --- a/tests/reactNativeEventsStore.spec.ts +++ /dev/null @@ -1,351 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, it, vi, expect } from 'vitest'; - - -const { mockMap, mockGet, mockSet, mockRemove, mockContains } = vi.hoisted(() => { - const mockMap = new Map(); - - const mockGet = vi.fn().mockImplementation((key) => { - return Promise.resolve(mockMap.get(key)); - }); - - const mockSet = vi.fn().mockImplementation((key, value) => { - mockMap.set(key, value); - return Promise.resolve(); - }); - - const mockRemove = vi.fn().mockImplementation((key) => { - if (mockMap.has(key)) { - mockMap.delete(key); - return Promise.resolve(true); - } - return Promise.resolve(false); - }); - - const mockContains = vi.fn().mockImplementation((key) => { - return Promise.resolve(mockMap.has(key)); - }); - - return { mockMap, mockGet, mockSet, mockRemove, mockContains }; -}); - -vi.mock('../lib/plugins/key_value_cache/reactNativeAsyncStorageCache', () => { - const MockReactNativeAsyncStorageCache = vi.fn(); - MockReactNativeAsyncStorageCache.prototype.get = mockGet; - MockReactNativeAsyncStorageCache.prototype.set = mockSet; - MockReactNativeAsyncStorageCache.prototype.contains = mockContains; - MockReactNativeAsyncStorageCache.prototype.remove = mockRemove; - return { 'default': MockReactNativeAsyncStorageCache }; -}); - -import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; - -import { ReactNativeEventsStore } from '../lib/event_processor/reactNativeEventsStore' - -const STORE_KEY = 'test-store' - -describe('ReactNativeEventsStore', () => { - const MockedReactNativeAsyncStorageCache = vi.mocked(ReactNativeAsyncStorageCache); - let store: ReactNativeEventsStore<any> - - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - store = new ReactNativeEventsStore(5, STORE_KEY) - }) - - describe('constructor', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('uses the user provided cache', () => { - const cache = { - get: vi.fn(), - contains: vi.fn(), - set: vi.fn(), - remove: vi.fn(), - }; - - const store = new ReactNativeEventsStore(5, STORE_KEY, cache); - store.clear(); - expect(cache.remove).toHaveBeenCalled(); - }); - - it('uses ReactNativeAsyncStorageCache if no cache is provided', () => { - const store = new ReactNativeEventsStore(5, STORE_KEY); - store.clear(); - expect(MockedReactNativeAsyncStorageCache).toHaveBeenCalledTimes(1); - expect(mockRemove).toHaveBeenCalled(); - }); - }); - - describe('set', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('should store all the events correctly in the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should store all the events when set asynchronously', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - }) - - describe('get', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('should correctly get items', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - expect(await store.get('event1')).toEqual({'name': 'event1'}) - expect(await store.get('event2')).toEqual({'name': 'event2'}) - expect(await store.get('event3')).toEqual({'name': 'event3'}) - expect(await store.get('event4')).toEqual({'name': 'event4'}) - }) - }) - - describe('getEventsMap', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('should get the whole map correctly', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const mapResult = await store.getEventsMap() - expect(mapResult).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - }) - - describe('getEventsList', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('should get all the events as a list', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const listResult = await store.getEventsList() - expect(listResult).toEqual([ - { "name": "event1" }, - { "name": "event2" }, - { "name": "event3" }, - { "name": "event4" }, - ]) - }) - }) - - describe('remove', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('should correctly remove items from the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event1') - storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event2') - storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should correctly remove items from the store when removed asynchronously', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - const promises = [] - await store.remove('event1') - await store.remove('event2') - await store.remove('event3') - storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ "event4": { "name": "event4" }}) - }) - }) - - describe('clear', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('should clear the whole store',async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.clear() - storedPendingEvents = storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY) || '{}'); - expect(storedPendingEvents).toEqual({}) - }) - }) - - describe('maxSize', () => { - beforeEach(() => { - MockedReactNativeAsyncStorageCache.mockClear(); - mockGet.mockClear(); - mockContains.mockClear(); - mockSet.mockClear(); - mockRemove.mockClear(); - mockMap.clear(); - }); - - it('should not add anymore events if the store if full', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - - let storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.set('event5', {'name': 'event5'}) - - storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - - await store.set('event6', {'name': 'event6'}) - storedPendingEvents = JSON.parse(mockMap.get(STORE_KEY)); - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - }) - }) -}) diff --git a/tests/reactNativeV1EventProcessor.spec.ts b/tests/reactNativeV1EventProcessor.spec.ts deleted file mode 100644 index 995dd6024..000000000 --- a/tests/reactNativeV1EventProcessor.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, it, vi, expect } from 'vitest'; - -vi.mock('@react-native-community/netinfo'); - -vi.mock('../lib/event_processor/reactNativeEventsStore'); - -import { ReactNativeEventsStore } from '../lib/event_processor/reactNativeEventsStore'; -import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; -import { LogTierV1EventProcessor } from '../lib/event_processor/index.react_native'; -import { PersistentCacheProvider } from '../lib/shared_types'; - -describe('LogTierV1EventProcessor', () => { - const MockedReactNativeEventsStore = vi.mocked(ReactNativeEventsStore); - - beforeEach(() => { - MockedReactNativeEventsStore.mockClear(); - }); - - it('calls the provided persistentCacheFactory and passes it to the ReactNativeEventStore constructor twice', async () => { - const getFakePersistentCache = () : PersistentKeyValueCache => { - return { - contains(k: string): Promise<boolean> { - return Promise.resolve(false); - }, - get(key: string): Promise<string | undefined> { - return Promise.resolve(undefined); - }, - remove(key: string): Promise<boolean> { - return Promise.resolve(false); - }, - set(key: string, val: string): Promise<void> { - return Promise.resolve() - } - }; - } - - let call = 0; - const fakeCaches = [getFakePersistentCache(), getFakePersistentCache()]; - const fakePersistentCacheProvider = vi.fn().mockImplementation(() => { - return fakeCaches[call++]; - }); - - const noop = () => {}; - - new LogTierV1EventProcessor({ - dispatcher: { dispatchEvent: () => Promise.resolve({}) }, - persistentCacheProvider: fakePersistentCacheProvider, - }) - - expect(fakePersistentCacheProvider).toHaveBeenCalledTimes(2); - expect(MockedReactNativeEventsStore.mock.calls[0][2] === fakeCaches[0]).toBeTruthy(); - expect(MockedReactNativeEventsStore.mock.calls[1][2] === fakeCaches[1]).toBeTruthy(); - }); -}); diff --git a/tests/requestTracker.spec.ts b/tests/requestTracker.spec.ts deleted file mode 100644 index 10c042a66..000000000 --- a/tests/requestTracker.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, it, expect } from 'vitest'; - -import RequestTracker from '../lib/event_processor/requestTracker' - -describe('requestTracker', () => { - describe('onRequestsComplete', () => { - it('returns an immediately-fulfilled promise when no requests are in flight', async () => { - const tracker = new RequestTracker() - await tracker.onRequestsComplete() - }) - - it('returns a promise that fulfills after in-flight requests are complete', async () => { - let resolveReq1: () => void - const req1 = new Promise<void>(resolve => { - resolveReq1 = resolve - }) - let resolveReq2: () => void - const req2 = new Promise<void>(resolve => { - resolveReq2 = resolve - }) - let resolveReq3: () => void - const req3 = new Promise<void>(resolve => { - resolveReq3 = resolve - }) - - const tracker = new RequestTracker() - tracker.trackRequest(req1) - tracker.trackRequest(req2) - tracker.trackRequest(req3) - - let reqsComplete = false - const reqsCompletePromise = tracker.onRequestsComplete().then(() => { - reqsComplete = true - }) - - resolveReq1!() - await req1 - expect(reqsComplete).toBe(false) - - resolveReq2!() - await req2 - expect(reqsComplete).toBe(false) - - resolveReq3!() - await req3 - await reqsCompletePromise - expect(reqsComplete).toBe(true) - }) - }) -}) diff --git a/tests/v1EventProcessor.react_native.spec.ts b/tests/v1EventProcessor.react_native.spec.ts deleted file mode 100644 index d0fccc4b0..000000000 --- a/tests/v1EventProcessor.react_native.spec.ts +++ /dev/null @@ -1,891 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, it, vi, expect, Mock } from 'vitest'; - -vi.mock('@react-native-community/netinfo'); -vi.mock('@react-native-async-storage/async-storage'); - -import { NotificationSender } from '../lib/core/notification_center' -import { NOTIFICATION_TYPES } from '../lib/utils/enums' - -import { LogTierV1EventProcessor } from '../lib/event_processor/v1/v1EventProcessor.react_native' -import { - EventDispatcher, - EventV1Request, - EventDispatcherResponse, -} from '../lib/event_processor/eventDispatcher' -import { EventProcessor, ProcessableEvent } from '../lib/event_processor/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/event_processor/v1/buildEventV1' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' -import { triggerInternetState } from '../__mocks__/@react-native-community/netinfo' -import { DefaultEventQueue } from '../lib/event_processor/eventQueue' -import { resolvablePromise, ResolvablePromise } from '../lib/utils/promise/resolvablePromise'; - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessorReactNative', () => { - describe('New Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: Mock - - beforeEach(() => { - dispatchStub = vi.fn().mockResolvedValue({ statusCode: 200 }) - - stubDispatcher = { - dispatchEvent: dispatchStub, - } - }) - - afterEach(() => { - vi.resetAllMocks() - AsyncStorage.clearStore() - }) - - describe('stop()', () => { - let resolvableResponse: ResolvablePromise<EventDispatcherResponse> - beforeEach(async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - resolvableResponse = resolvablePromise<EventDispatcherResponse>() - return resolvableResponse.promise - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - await processor.stop() - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - - resolvableResponse.resolve({ statusCode: 200 }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', async () => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let responsePromise: ResolvablePromise<EventDispatcherResponse> - stubDispatcher = { - dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { - dispatchStub(event) - responsePromise = resolvablePromise<EventDispatcherResponse>() - return responsePromise.promise; - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - - resolvableResponse.resolve({ statusCode: 400 }) - }) - - it('should return a promise when multiple event batches are sent', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - return Promise.resolve({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 150)) - await processor.stop() - expect(dispatchStub).toBeCalledTimes(2) - }) - - it('should stop accepting events after stop is called', async () => { - const dispatcher = { - dispatchEvent: vi.fn((event: EventV1Request) => { - return new Promise<EventDispatcherResponse>(resolve => { - setTimeout(() => resolve({ statusCode: 204 }), 0) - }) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - await new Promise(resolve => setTimeout(resolve, 150)) - - await processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should immediately flush events as they are processed', async () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 300', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', async () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the queue when the flush interval happens', async () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - await new Promise(resolve => setTimeout(resolve, 350)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - - // clear the async storate cache to ensure next tests - // works correctly - await new Promise(resolve => setTimeout(resolve, 400)) - }) - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', async () => { - const dispatcher: EventDispatcher = { - dispatchEvent: vi.fn().mockResolvedValue({ statusCode: 200 }) - } - - const notificationCenter: NotificationSender = { - sendNotifications: vi.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - await processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid batchSize', () => { - it('should ignore a batchSize of 0 and use the default', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) - }) - - describe('Pending Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: Mock - - beforeEach(() => { - dispatchStub = vi.fn() - }) - - afterEach(() => { - vi.clearAllMocks() - AsyncStorage.clearStore() - }) - - describe('Retry Pending Events', () => { - describe('App start', () => { - it('should dispatch all the pending events in correct order', async () => { - let receivedEvents: EventV1Request[] = [] - - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - return Promise.resolve({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - let event3 = createConversionEvent() - event3.user.id = 'user3' - let event4 = createConversionEvent() - event4.user.id = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - - vi.clearAllMocks() - - receivedEvents = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - receivedEvents.push(event) - dispatchStub(event) - return Promise.resolve({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - - receivedEvents.forEach((e, i) => { - expect(e.params.visitors[0].visitor_id).toEqual(`user${i+1}`) - }) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - }) - - it('should process all the events left in buffer when the app closed last time', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - return Promise.resolve({ statusCode: 200 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 1000, - batchSize: 4, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - event2.uuid = 'user2' - - processor.process(event1) - processor.process(event2) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Explicitly stopping the timer to simulate app close - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - let receivedEvents: EventV1Request[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - receivedEvents.push(event) - dispatchStub(event) - return Promise.resolve({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 4, - }) - - await processor.start() - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toBeCalledTimes(1) - expect(receivedEvents.length).toEqual(1) - const receivedEvent = receivedEvents[0] - - receivedEvent.params.visitors.forEach((v, i) => { - expect(v.visitor_id).toEqual(`user${i+1}`) - }) - - await processor.stop() - }) - - it('should dispatch pending events first and then process events in buffer store', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - return Promise.resolve({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - - await processor.start() - - for (let i = 0; i < 8; i++) { - let event = createConversionEvent() - event.user.id = `user${i}` - event.uuid = `user${i}` - processor.process(event) - } - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toBeCalledTimes(2) - - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - vi.clearAllMocks() - - const visitorIds: string[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - event.params.visitors.forEach(visitor => visitorIds.push(visitor.visitor_id)) - return Promise.resolve({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 200, - batchSize: 3, - }) - - await processor.start() - - expect(dispatchStub).toBeCalledTimes(2) - - await new Promise(resolve => setTimeout(resolve, 250)) - expect(visitorIds.length).toEqual(8) - expect(visitorIds).toEqual(['user0', 'user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7']) - }) - }) - - describe('When a new event is dispatched', () => { - it('should dispatch all the pending events first and then new event in correct order', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - return Promise.resolve({ statusCode: 200 }) - } else { - return Promise.resolve({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - vi.resetAllMocks() - - let event5 = createConversionEvent() - event5.user.id = event5.uuid = 'user5' - - processor.process(event5) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(5) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4', 'user5']) - await processor.stop() - }) - - it('should skip dispatching subsequent events if an event fails to dispatch', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - dispatchCount++ - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - return Promise.resolve({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(1) - - processor.process(event2) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(2) - - processor.process(event3) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(3) - - processor.process(event4) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - - expect(dispatchCount).toEqual(4) - - // subsequent events were skipped with each attempt because of request failure - expect(receivedVisitorIds).toEqual(['user1', 'user1', 'user1', 'user1']) - await processor.stop() - }) - }) - - describe('When internet connection is restored', () => { - it('should dispatch all the pending events in correct order when internet connection is restored', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - return Promise.resolve({ statusCode: 200 }) - } else { - return Promise.resolve({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 50)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - vi.resetAllMocks() - - triggerInternetState(true) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - - it('should not dispatch duplicate events if internet is lost and restored twice in a short interval', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request) { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - return Promise.resolve({ statusCode: 200 }) - } else { - return Promise.resolve({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - vi.resetAllMocks() - - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - }) - }) - }) -}) diff --git a/tests/v1EventProcessor.spec.ts b/tests/v1EventProcessor.spec.ts deleted file mode 100644 index bd7333bee..000000000 --- a/tests/v1EventProcessor.spec.ts +++ /dev/null @@ -1,582 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, it, vi, expect, Mock } from 'vitest'; - -import { LogTierV1EventProcessor } from '../lib/event_processor/v1/v1EventProcessor' -import { - EventDispatcher, - EventV1Request, - EventDispatcherResponse, -} from '../lib/event_processor/eventDispatcher' -import { EventProcessor } from '../lib/event_processor/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/event_processor/v1/buildEventV1' -import { NotificationCenter, NotificationSender } from '../lib/core/notification_center' -import { NOTIFICATION_TYPES } from '../lib/utils/enums' -import { resolvablePromise, ResolvablePromise } from '../lib/utils/promise/resolvablePromise'; - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessor', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: Mock - // TODO change this to ProjectConfig when js-sdk-models is available - let testProjectConfig: any - - beforeEach(() => { - vi.useFakeTimers() - - testProjectConfig = {} - dispatchStub = vi.fn() - - stubDispatcher = { - dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { - dispatchStub(event) - return Promise.resolve({ statusCode: 200 }) - }, - } - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - describe('stop()', () => { - let resposePromise: ResolvablePromise<EventDispatcherResponse> - beforeEach(() => { - stubDispatcher = { - dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { - dispatchStub(event) - return Promise.resolve({ statusCode: 200 }) - }, - } - stubDispatcher = { - dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { - dispatchStub(event) - resposePromise = resolvablePromise() - return resposePromise.promise - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', () => - new Promise<void>((done) => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - processor.stop().then(() => { - done() - }) - }) - ) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', () => - new Promise<void>((done) => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - - resposePromise.resolve({ statusCode: 200 }) - }) - ) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', () => - new Promise<void>((done) => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - stubDispatcher = { - dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { - dispatchStub(event) - resposePromise = resolvablePromise() - return Promise.resolve({statusCode: 400}) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - }) - ) - - it('should return a promise when multiple event batches are sent', () => - new Promise<void>((done) => { - stubDispatcher = { - dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> { - dispatchStub(event) - return Promise.resolve({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - processor.stop().then(() => { - expect(dispatchStub).toBeCalledTimes(2) - done() - }) - }) - ) - - it('should stop accepting events after stop is called', () => { - const dispatcher = { - dispatchEvent: vi.fn((event: EventV1Request) => { - return new Promise<EventDispatcherResponse>((resolve) => { - setTimeout(() => resolve({ statusCode: 204 }), 0) - }) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - - it('should resolve the stop promise after all dispatcher requests are done', async () => { - const dispatchPromises: Array<ResolvablePromise<EventDispatcherResponse>> = [] - const dispatcher = { - dispatchEvent: vi.fn((event: EventV1Request) => { - const response = resolvablePromise<EventDispatcherResponse>(); - dispatchPromises.push(response); - return response.promise; - }) - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 2, - }) - processor.start() - - for (let i = 0; i < 4; i++) { - processor.process(createImpressionEvent()) - } - expect(dispatchPromises.length).toBe(2) - - let stopPromiseResolved = false - const stopPromise = processor.stop().then(() => { - stopPromiseResolved = true - }) - expect(stopPromiseResolved).toBe(false) - - dispatchPromises[0].resolve({ statusCode: 204 }) - vi.advanceTimersByTime(100) - expect(stopPromiseResolved).toBe(false) - dispatchPromises[1].resolve({ statusCode: 204 }) - await stopPromise - expect(stopPromiseResolved).toBe(true) - }) - - it('should use the provided closingDispatcher to dispatch events on stop', async () => { - const dispatcher = { - dispatchEvent: vi.fn(), - } - - const closingDispatcher = { - dispatchEvent: vi.fn(), - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - closingDispatcher, - flushInterval: 100000, - batchSize: 20, - }); - - processor.start() - - const events : any = []; - - for (let i = 0; i < 4; i++) { - const event = createImpressionEvent(); - processor.process(event); - events.push(event); - } - - processor.stop(); - vi.runAllTimers(); - - expect(dispatcher.dispatchEvent).not.toHaveBeenCalled(); - expect(closingDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - - const [data] = closingDispatcher.dispatchEvent.mock.calls[0]; - expect(data.params).toEqual(makeBatchedEventV1(events)); - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should immediately flush events as they are processed', () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 100', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the queue when the flush interval happens', () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - vi.advanceTimersByTime(100) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - }) - - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', async () => { - const dispatcher: EventDispatcher = { - dispatchEvent: vi.fn().mockResolvedValue({ statusCode: 200 }) - } - - const notificationCenter: NotificationSender = { - sendNotifications: vi.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid flushInterval or batchSize', () => { - it('should ignore a flushInterval of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 0, - batchSize: 10, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - vi.advanceTimersByTime(30000) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - }) - - it('should ignore a batchSize of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) -}) From 6930199f4a154fb17169d743a8a12a25046c234e Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:32:59 +0600 Subject: [PATCH 094/200] [FSSDK-10882] ProjectConfigManager SSR support (#965) * [FSSDK-10882] ssr support addition --- lib/index.node.tests.js | 1 - lib/optimizely/index.spec.ts | 78 +++++++++++++++++++ lib/optimizely/index.ts | 1 + .../project_config_manager.spec.ts | 21 +++++ lib/project_config/project_config_manager.ts | 22 +++++- lib/shared_types.ts | 2 + lib/tests/mock/mock_project_config_manager.ts | 4 + 7 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 lib/optimizely/index.spec.ts diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index aa0f8743e..3495b036b 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -86,7 +86,6 @@ describe('optimizelyFactory', function() { assert.instanceOf(optlyInstance, Optimizely); assert.equal(optlyInstance.clientVersion, '5.3.4'); }); - // TODO: user will create and inject an event processor // these tests will be refactored accordingly // describe('event processor configuration', function() { diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts new file mode 100644 index 000000000..a4b88017f --- /dev/null +++ b/lib/optimizely/index.spec.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; +import Optimizely from '.'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import * as logger from '../plugins/logger'; +import * as jsonSchemaValidator from '../utils/json_schema_validator'; +import { LOG_LEVEL } from '../common_exports'; +import { createNotificationCenter } from '../core/notification_center'; +import testData from '../tests/test_data'; +import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; +import { LoggerFacade } from '../modules/logging'; +import { createProjectConfig } from '../project_config/project_config'; + +describe('lib/optimizely', () => { + const errorHandler = { handleError: function() {} }; + + const eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + + const createdLogger: LoggerFacade = { + ...logger.createLogger({ + logLevel: LOG_LEVEL.INFO, + }), + info: () => {}, + debug: () => {}, + warn: () => {}, + error: () => {}, + log: () => {}, + }; + + const notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); + + it('should pass ssr to the project config manager', () => { + const projectConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + + vi.spyOn(projectConfigManager, 'setSsr'); + + const instance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + errorHandler, + jsonSchemaValidator, + logger: createdLogger, + notificationCenter, + eventProcessor, + isSsr: true, + isValidInstance: true, + }); + + expect(projectConfigManager.setSsr).toHaveBeenCalledWith(true); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(instance.getProjectConfig()).toBe(projectConfigManager.config); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(projectConfigManager.isSsr).toBe(true); + }); +}); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index f9b29a6b4..a15a7711f 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -144,6 +144,7 @@ export default class Optimizely implements Client { this.updateOdpSettings(); }); + this.projectConfigManager.setSsr(config.isSsr) this.projectConfigManager.start(); const projectConfigManagerRunningPromise = this.projectConfigManager.onRunning(); diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts index 5a568188d..967aec83c 100644 --- a/lib/project_config/project_config_manager.spec.ts +++ b/lib/project_config/project_config_manager.spec.ts @@ -165,6 +165,17 @@ describe('ProjectConfigManagerImpl', () => { await manager.onRunning(); expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); }); + + it('should not start datafileManager if isSsr is true and return correct config', () => { + const datafileManager = getMockDatafileManager({}); + vi.spyOn(datafileManager, 'start'); + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.setSsr(true); + manager.start(); + + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + expect(datafileManager.start).not.toHaveBeenCalled(); + }); }); describe('when datafile is invalid', () => { @@ -398,6 +409,16 @@ describe('ProjectConfigManagerImpl', () => { expect(logger.error).toHaveBeenCalled(); }); + it('should reject onRunning() and log error if isSsr is true and datafile is not provided', async () =>{ + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafileManager: getMockDatafileManager({})}); + manager.setSsr(true); + manager.start(); + + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + it('should reject onRunning() and log error if the datafile version is not supported', async () => { const logger = getMockLogger(); const datafile = testData.getUnsupportedVersionConfig(); diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index 94c83902b..46c79238c 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -34,6 +34,7 @@ interface ProjectConfigManagerConfig { export interface ProjectConfigManager extends Service { setLogger(logger: LoggerFacade): void; + setSsr(isSsr?: boolean): void; getConfig(): ProjectConfig | undefined; getOptimizelyConfig(): OptimizelyConfig | undefined; onUpdate(listener: Consumer<ProjectConfig>): Fn; @@ -53,6 +54,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf public jsonSchemaValidator?: Transformer<unknown, boolean>; public datafileManager?: DatafileManager; private eventEmitter: EventEmitter<{ update: ProjectConfig }> = new EventEmitter(); + private isSsr = false; constructor(config: ProjectConfigManagerConfig) { super(); @@ -68,9 +70,18 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf } this.state = ServiceState.Starting; + + if(this.isSsr) { + // If isSsr is true, we don't need to poll for datafile updates + this.datafileManager = undefined + } + if (!this.datafile && !this.datafileManager) { + const errorMessage = this.isSsr + ? 'You must provide datafile in SSR' + : 'You must provide at least one of sdkKey or datafile'; // TODO: replace message with imported constants - this.handleInitError(new Error('You must provide at least one of sdkKey or datafile')); + this.handleInitError(new Error(errorMessage)); return; } @@ -211,4 +222,13 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf this.stopPromise.reject(err); }); } + + /** + * Set the isSsr flag to indicate if the project config manager is being used in a server side rendering environment + * @param {Boolean} isSsr + * @returns {void} + */ + setSsr(isSsr: boolean): void { + this.isSsr = isSsr; + } } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index f27657378..b5249266f 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -292,6 +292,7 @@ export interface OptimizelyOptions { sdkKey?: string; userProfileService?: UserProfileService | null; defaultDecideOptions?: OptimizelyDecideOption[]; + isSsr?:boolean; odpManager?: IOdpManager; notificationCenter: NotificationCenterImpl; } @@ -426,6 +427,7 @@ export interface ConfigLite { defaultDecideOptions?: OptimizelyDecideOption[]; clientEngine?: string; clientVersion?: string; + isSsr?: boolean; } export type OptimizelyExperimentsMap = { diff --git a/lib/tests/mock/mock_project_config_manager.ts b/lib/tests/mock/mock_project_config_manager.ts index af7a8ba84..b76f71e2d 100644 --- a/lib/tests/mock/mock_project_config_manager.ts +++ b/lib/tests/mock/mock_project_config_manager.ts @@ -26,8 +26,12 @@ type MockOpt = { export const getMockProjectConfigManager = (opt: MockOpt = {}): ProjectConfigManager => { return { + isSsr: false, config: opt.initConfig, start: () => {}, + setSsr: function(isSsr:boolean) { + this.isSsr = isSsr; + }, onRunning: () => opt.onRunning || Promise.resolve(), stop: () => {}, onTerminated: () => opt.onTerminated || Promise.resolve(), From 61053364dfa001137216edf70b955a3add0484fe Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 25 Nov 2024 20:46:27 +0600 Subject: [PATCH 095/200] [FSSDK-10941] event processor files and directories cleanup - part 1 (#966) --- ...batch_event_processor.react_native.spec.ts | 10 +- .../batch_event_processor.spec.ts | 7 +- lib/event_processor/batch_event_processor.ts | 6 +- .../default_dispatcher.browser.ts | 2 +- .../default_dispatcher.node.ts | 2 +- .../default_dispatcher.spec.ts | 2 +- lib/event_processor/default_dispatcher.ts | 2 +- .../event_builder/build_event_v1.spec.ts | 812 ++++++++++++++++++ .../event_builder/build_event_v1.ts | 2 +- .../event_builder/event_helpers.tests.js | 2 +- .../event_builder/event_helpers.ts | 4 +- .../event_builder/index.tests.js | 0 .../event_builder/index.ts | 2 +- ...eventDispatcher.ts => event_dispatcher.ts} | 2 +- .../{eventProcessor.ts => event_processor.ts} | 2 +- .../event_processor_factory.browser.spec.ts | 2 +- .../event_processor_factory.browser.ts | 6 +- .../event_processor_factory.node.ts | 4 +- .../event_processor_factory.react_native.ts | 4 +- .../event_processor_factory.ts | 4 +- .../forwarding_event_processor.spec.ts | 4 +- .../forwarding_event_processor.ts | 6 +- .../send_beacon_dispatcher.browser.spec.ts | 6 +- .../send_beacon_dispatcher.browser.ts} | 2 +- lib/event_processor/v1/buildEventV1.ts | 272 ------ lib/index.browser.ts | 2 +- lib/index.lite.tests.js | 1 - lib/index.lite.ts | 3 - lib/optimizely/index.ts | 6 +- lib/plugins/event_dispatcher/no_op.ts | 33 - lib/shared_types.ts | 8 +- tests/buildEventV1.spec.ts | 812 ------------------ tsconfig.spec.json | 14 +- 33 files changed, 873 insertions(+), 1173 deletions(-) create mode 100644 lib/event_processor/event_builder/build_event_v1.spec.ts rename lib/{core => event_processor}/event_builder/build_event_v1.ts (99%) rename lib/{core => event_processor}/event_builder/event_helpers.tests.js (99%) rename lib/{core => event_processor}/event_builder/event_helpers.ts (98%) rename lib/{core => event_processor}/event_builder/index.tests.js (100%) rename lib/{core => event_processor}/event_builder/index.ts (99%) rename lib/event_processor/{eventDispatcher.ts => event_dispatcher.ts} (93%) rename lib/event_processor/{eventProcessor.ts => event_processor.ts} (95%) rename tests/sendBeaconDispatcher.spec.ts => lib/event_processor/send_beacon_dispatcher.browser.spec.ts (92%) rename lib/{plugins/event_dispatcher/send_beacon_dispatcher.ts => event_processor/send_beacon_dispatcher.browser.ts} (93%) delete mode 100644 lib/event_processor/v1/buildEventV1.ts delete mode 100644 lib/plugins/event_dispatcher/no_op.ts delete mode 100644 tests/buildEventV1.spec.ts diff --git a/lib/event_processor/batch_event_processor.react_native.spec.ts b/lib/event_processor/batch_event_processor.react_native.spec.ts index 68ccd6016..ea1612f4c 100644 --- a/lib/event_processor/batch_event_processor.react_native.spec.ts +++ b/lib/event_processor/batch_event_processor.react_native.spec.ts @@ -18,8 +18,8 @@ import { vi, describe, it, expect, beforeEach } from 'vitest'; const mockNetInfo = vi.hoisted(() => { const netInfo = { - listeners: [], - unsubs: [], + listeners: [] as any[], + unsubs: [] as any[], addEventListener(fn: any) { this.listeners.push(fn); const unsub = vi.fn(); @@ -46,15 +46,13 @@ vi.mock('../utils/import.react_native/@react-native-community/netinfo', () => { }); import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; -import { getMockLogger } from '../tests/mock/mock_logger'; import { getMockRepeater } from '../tests/mock/mock_repeater'; import { getMockAsyncCache } from '../tests/mock/mock_cache'; import { EventWithId } from './batch_event_processor'; -import { EventDispatcher } from './eventDispatcher'; -import { formatEvents } from './v1/buildEventV1'; +import { formatEvents } from './event_builder/build_event_v1'; import { createImpressionEvent } from '../tests/mock/create_event'; -import { ProcessableEvent } from './eventProcessor'; +import { ProcessableEvent } from './event_processor'; const getMockDispatcher = () => { return { diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index 715b4452b..2c81f9215 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -18,10 +18,9 @@ import { expect, describe, it, vi, beforeEach, afterEach, MockInstance } from 'v import { EventWithId, BatchEventProcessor } from './batch_event_processor'; import { getMockSyncCache } from '../tests/mock/mock_cache'; import { createImpressionEvent } from '../tests/mock/create_event'; -import { ProcessableEvent } from './eventProcessor'; -import { EventDispatcher } from './eventDispatcher'; -import { formatEvents } from './v1/buildEventV1'; -import { ResolvablePromise, resolvablePromise } from '../utils/promise/resolvablePromise'; +import { ProcessableEvent } from './event_processor'; +import { formatEvents } from './event_builder/build_event_v1'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { advanceTimersByTime } from '../../tests/testUtils'; import { getMockLogger } from '../tests/mock/mock_logger'; import { getMockRepeater } from '../tests/mock/mock_repeater'; diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 7cad445cd..3d000a5df 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { EventProcessor, ProcessableEvent } from "./eventProcessor"; +import { EventProcessor, ProcessableEvent } from "./event_processor"; import { Cache } from "../utils/cache/cache"; -import { EventDispatcher, EventDispatcherResponse, EventV1Request } from "./eventDispatcher"; -import { formatEvents } from "../core/event_builder/build_event_v1"; +import { EventDispatcher, EventDispatcherResponse, EventV1Request } from "./event_dispatcher"; +import { formatEvents } from "./event_builder/build_event_v1"; import { BackoffController, ExponentialBackoff, IntervalRepeater, Repeater } from "../utils/repeater/repeater"; import { LoggerFacade } from "../modules/logging"; import { BaseService, ServiceState, StartupLog } from "../service"; diff --git a/lib/event_processor/default_dispatcher.browser.ts b/lib/event_processor/default_dispatcher.browser.ts index d4601700c..1dd72ab00 100644 --- a/lib/event_processor/default_dispatcher.browser.ts +++ b/lib/event_processor/default_dispatcher.browser.ts @@ -15,7 +15,7 @@ */ import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; -import { EventDispatcher } from '../event_processor/eventDispatcher'; +import { EventDispatcher } from './event_dispatcher'; import { DefaultEventDispatcher } from './default_dispatcher'; const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new BrowserRequestHandler()); diff --git a/lib/event_processor/default_dispatcher.node.ts b/lib/event_processor/default_dispatcher.node.ts index 75e00aff3..130eaa6d2 100644 --- a/lib/event_processor/default_dispatcher.node.ts +++ b/lib/event_processor/default_dispatcher.node.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventDispatcher } from '../event_processor/eventDispatcher'; +import { EventDispatcher } from './event_dispatcher'; import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; import { DefaultEventDispatcher } from './default_dispatcher'; diff --git a/lib/event_processor/default_dispatcher.spec.ts b/lib/event_processor/default_dispatcher.spec.ts index 0616ba3bf..f7cdc718f 100644 --- a/lib/event_processor/default_dispatcher.spec.ts +++ b/lib/event_processor/default_dispatcher.spec.ts @@ -15,7 +15,7 @@ */ import { expect, vi, describe, it } from 'vitest'; import { DefaultEventDispatcher } from './default_dispatcher'; -import { EventV1 } from '../event_processor'; +import { EventV1 } from './event_builder/build_event_v1'; const getEvent = (): EventV1 => { return { diff --git a/lib/event_processor/default_dispatcher.ts b/lib/event_processor/default_dispatcher.ts index ce8dd5b59..3105b49e1 100644 --- a/lib/event_processor/default_dispatcher.ts +++ b/lib/event_processor/default_dispatcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { RequestHandler } from '../utils/http_request_handler/http'; -import { EventDispatcher, EventDispatcherResponse, EventV1Request } from '../event_processor/eventDispatcher'; +import { EventDispatcher, EventDispatcherResponse, EventV1Request } from './event_dispatcher'; export class DefaultEventDispatcher implements EventDispatcher { private requestHandler: RequestHandler; diff --git a/lib/event_processor/event_builder/build_event_v1.spec.ts b/lib/event_processor/event_builder/build_event_v1.spec.ts new file mode 100644 index 000000000..b1082dc7e --- /dev/null +++ b/lib/event_processor/event_builder/build_event_v1.spec.ts @@ -0,0 +1,812 @@ +/** + * Copyright 2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; + +import { + buildConversionEventV1, + buildImpressionEventV1, + makeBatchedEventV1, +} from './build_event_v1'; + +import { ImpressionEvent, ConversionEvent } from '../events' + +describe('buildImpressionEventV1', () => { + it('should build an ImpressionEventV1 when experiment and variation are defined', () => { + const impressionEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: 'layerId', + }, + + experiment: { + id: 'expId', + key: 'expKey', + }, + + variation: { + id: 'varId', + key: 'varKey', + }, + + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true, + } + + const result = buildImpressionEventV1(impressionEvent) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + decisions: [ + { + campaign_id: 'layerId', + experiment_id: 'expId', + variation_id: 'varId', + metadata: { + flag_key: 'flagKey1', + rule_key: 'expKey', + rule_type: 'experiment', + variation_key: 'varKey', + enabled: true, + }, + }, + ], + events: [ + { + entity_id: 'layerId', + timestamp: 69, + key: 'campaign_activated', + uuid: 'uuid', + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should build an ImpressionEventV1 when experiment and variation are not defined', () => { + const impressionEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: null, + }, + + experiment: { + id: null, + key: '', + }, + + variation: { + id: null, + key: '', + }, + + ruleKey: '', + flagKey: 'flagKey1', + ruleType: 'rollout', + enabled: true, + } + + const result = buildImpressionEventV1(impressionEvent) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + decisions: [ + { + campaign_id: null, + experiment_id: "", + variation_id: "", + metadata: { + flag_key: 'flagKey1', + rule_key: '', + rule_type: 'rollout', + variation_key: '', + enabled: true, + }, + }, + ], + events: [ + { + entity_id: null, + timestamp: 69, + key: 'campaign_activated', + uuid: 'uuid', + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) +}) + +describe('buildConversionEventV1', () => { + it('should build a ConversionEventV1 when tags object is defined', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + + revenue: 1000, + value: 123, + } + + const result = buildConversionEventV1(conversionEvent) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should build a ConversionEventV1 when tags object is undefined', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: undefined, + + revenue: 1000, + value: 123, + } + + const result = buildConversionEventV1(conversionEvent) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: undefined, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should build a ConversionEventV1 when event id is null', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: null, + key: 'event-key', + }, + + tags: undefined, + + revenue: 1000, + value: 123, + } + + const result = buildConversionEventV1(conversionEvent) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: null, + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: undefined, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should include revenue and value if they are 0', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: 0, + revenue: 0, + }, + + revenue: 0, + value: 0, + } + + const result = buildConversionEventV1(conversionEvent) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: 0, + revenue: 0, + }, + revenue: 0, + value: 0, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should not include $opt_bot_filtering attribute if context.botFiltering is undefined', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + + revenue: 1000, + value: 123, + } + + const result = buildConversionEventV1(conversionEvent) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + ], + }, + ], + }) + }) +}) + +describe('makeBatchedEventV1', () => { + it('should batch Conversion and Impression events together', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + + revenue: 1000, + value: 123, + } + + const impressionEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: 'layerId', + }, + + experiment: { + id: 'expId', + key: 'expKey', + }, + + variation: { + id: 'varId', + key: 'varKey', + }, + + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true, + } + + const result = makeBatchedEventV1([impressionEvent, conversionEvent]) + + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + decisions: [ + { + campaign_id: 'layerId', + experiment_id: 'expId', + variation_id: 'varId', + metadata: { + flag_key: 'flagKey1', + rule_key: 'expKey', + rule_type: 'experiment', + variation_key: 'varKey', + enabled: true, + }, + }, + ], + events: [ + { + entity_id: 'layerId', + timestamp: 69, + key: 'campaign_activated', + uuid: 'uuid', + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) +}) + diff --git a/lib/core/event_builder/build_event_v1.ts b/lib/event_processor/event_builder/build_event_v1.ts similarity index 99% rename from lib/core/event_builder/build_event_v1.ts rename to lib/event_processor/event_builder/build_event_v1.ts index 0479dc79a..2cd794ca0 100644 --- a/lib/core/event_builder/build_event_v1.ts +++ b/lib/event_processor/event_builder/build_event_v1.ts @@ -17,7 +17,7 @@ import { EventTags, ConversionEvent, ImpressionEvent, -} from '../../event_processor/events'; +} from '../events'; import { Event } from '../../shared_types'; diff --git a/lib/core/event_builder/event_helpers.tests.js b/lib/event_processor/event_builder/event_helpers.tests.js similarity index 99% rename from lib/core/event_builder/event_helpers.tests.js rename to lib/event_processor/event_builder/event_helpers.tests.js index 552a72e24..b241ecaf0 100644 --- a/lib/core/event_builder/event_helpers.tests.js +++ b/lib/event_processor/event_builder/event_helpers.tests.js @@ -18,7 +18,7 @@ import { assert } from 'chai'; import fns from '../../utils/fns'; import * as projectConfig from '../../project_config/project_config'; -import * as decision from '../decision'; +import * as decision from '../../core/decision'; import { buildImpressionEvent, buildConversionEvent } from './event_helpers'; describe('lib/event_builder/event_helpers', function() { diff --git a/lib/core/event_builder/event_helpers.ts b/lib/event_processor/event_builder/event_helpers.ts similarity index 98% rename from lib/core/event_builder/event_helpers.ts rename to lib/event_processor/event_builder/event_helpers.ts index 9c0fc8257..58b5cdb08 100644 --- a/lib/core/event_builder/event_helpers.ts +++ b/lib/event_processor/event_builder/event_helpers.ts @@ -18,10 +18,10 @@ import { getLogger } from '../../modules/logging'; import fns from '../../utils/fns'; import * as eventTagUtils from '../../utils/event_tag_utils'; import * as attributesValidator from '../../utils/attributes_validator'; -import * as decision from '../decision'; +import * as decision from '../../core/decision'; import { EventTags, UserAttributes } from '../../shared_types'; -import { DecisionObj } from '../decision_service'; +import { DecisionObj } from '../../core/decision_service'; import { getAttributeId, getEventId, diff --git a/lib/core/event_builder/index.tests.js b/lib/event_processor/event_builder/index.tests.js similarity index 100% rename from lib/core/event_builder/index.tests.js rename to lib/event_processor/event_builder/index.tests.js diff --git a/lib/core/event_builder/index.ts b/lib/event_processor/event_builder/index.ts similarity index 99% rename from lib/core/event_builder/index.ts rename to lib/event_processor/event_builder/index.ts index 20efd53c7..813038f05 100644 --- a/lib/core/event_builder/index.ts +++ b/lib/event_processor/event_builder/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { LoggerFacade } from '../../modules/logging'; -import { EventV1 as CommonEventParams } from '../../event_processor/v1/buildEventV1'; +import { EventV1 as CommonEventParams } from '../event_builder/build_event_v1'; import fns from '../../utils/fns'; import { CONTROL_ATTRIBUTES, RESERVED_EVENT_KEYWORDS } from '../../utils/enums'; diff --git a/lib/event_processor/eventDispatcher.ts b/lib/event_processor/event_dispatcher.ts similarity index 93% rename from lib/event_processor/eventDispatcher.ts rename to lib/event_processor/event_dispatcher.ts index 90b036862..3872e6e90 100644 --- a/lib/event_processor/eventDispatcher.ts +++ b/lib/event_processor/event_dispatcher.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventV1 } from "./v1/buildEventV1"; +import { EventV1 } from "./event_builder/build_event_v1"; export type EventDispatcherResponse = { statusCode?: number diff --git a/lib/event_processor/eventProcessor.ts b/lib/event_processor/event_processor.ts similarity index 95% rename from lib/event_processor/eventProcessor.ts rename to lib/event_processor/event_processor.ts index 656beab90..1aee1a857 100644 --- a/lib/event_processor/eventProcessor.ts +++ b/lib/event_processor/event_processor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ConversionEvent, ImpressionEvent } from './events' -import { EventV1Request } from './eventDispatcher' +import { EventV1Request } from './event_dispatcher' import { getLogger } from '../modules/logging' import { Service } from '../service' import { Consumer, Fn } from '../utils/type'; diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts index 5bd615ebe..e35dd1908 100644 --- a/lib/event_processor/event_processor_factory.browser.spec.ts +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -48,7 +48,7 @@ import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { SyncPrefixCache } from '../utils/cache/cache'; import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.browser'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; -import sendBeaconEventDispatcher from '../plugins/event_dispatcher/send_beacon_dispatcher'; +import sendBeaconEventDispatcher from './send_beacon_dispatcher.browser'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import browserDefaultEventDispatcher from './default_dispatcher.browser'; import { getBatchEventProcessor } from './event_processor_factory'; diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index 476186030..9456d06b1 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -15,12 +15,12 @@ */ import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './eventDispatcher'; -import { EventProcessor } from './eventProcessor'; +import { EventDispatcher } from './event_dispatcher'; +import { EventProcessor } from './event_processor'; import { EventWithId } from './batch_event_processor'; import { getBatchEventProcessor, BatchEventProcessorOptions } from './event_processor_factory'; import defaultEventDispatcher from './default_dispatcher.browser'; -import sendBeaconEventDispatcher from '../plugins/event_dispatcher/send_beacon_dispatcher'; +import sendBeaconEventDispatcher from './send_beacon_dispatcher.browser'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { SyncPrefixCache } from '../utils/cache/cache'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index 7bfd43c6a..1a21fbf60 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './eventDispatcher'; -import { EventProcessor } from './eventProcessor'; +import { EventDispatcher } from './event_dispatcher'; +import { EventProcessor } from './event_processor'; import defaultEventDispatcher from './default_dispatcher.node'; import { BatchEventProcessorOptions, FAILED_EVENT_RETRY_INTERVAL, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index 84c11e375..a007501a5 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './eventDispatcher'; -import { EventProcessor } from './eventProcessor'; +import { EventDispatcher } from './event_dispatcher'; +import { EventProcessor } from './event_processor'; import defaultEventDispatcher from './default_dispatcher.browser'; import { BatchEventProcessorOptions, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index 3e2cc0d7c..adba35c1d 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -17,8 +17,8 @@ import { LogLevel } from "../common_exports"; import { StartupLog } from "../service"; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; -import { EventDispatcher } from "./eventDispatcher"; -import { EventProcessor } from "./eventProcessor"; +import { EventDispatcher } from "./event_dispatcher"; +import { EventProcessor } from "./event_processor"; import { BatchEventProcessor, EventWithId, RetryConfig } from "./batch_event_processor"; import { AsyncPrefixCache, Cache, SyncPrefixCache } from "../utils/cache/cache"; diff --git a/lib/event_processor/forwarding_event_processor.spec.ts b/lib/event_processor/forwarding_event_processor.spec.ts index 41393109a..3675c010f 100644 --- a/lib/event_processor/forwarding_event_processor.spec.ts +++ b/lib/event_processor/forwarding_event_processor.spec.ts @@ -16,8 +16,8 @@ import { expect, describe, it, vi } from 'vitest'; import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './eventDispatcher'; -import { formatEvents, makeBatchedEventV1 } from './v1/buildEventV1'; +import { EventDispatcher } from './event_dispatcher'; +import { formatEvents, makeBatchedEventV1 } from './event_builder/build_event_v1'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ServiceState } from '../service'; diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 1fc06ebc9..99bccabd2 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -15,11 +15,11 @@ */ -import { EventV1Request } from './eventDispatcher'; -import { EventProcessor, ProcessableEvent } from './eventProcessor'; +import { EventV1Request } from './event_dispatcher'; +import { EventProcessor, ProcessableEvent } from './event_processor'; import { EventDispatcher } from '../shared_types'; -import { formatEvents } from '../core/event_builder/build_event_v1'; +import { formatEvents } from './event_builder/build_event_v1'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; diff --git a/tests/sendBeaconDispatcher.spec.ts b/lib/event_processor/send_beacon_dispatcher.browser.spec.ts similarity index 92% rename from tests/sendBeaconDispatcher.spec.ts rename to lib/event_processor/send_beacon_dispatcher.browser.spec.ts index 3b69ffc27..06bd5bd1f 100644 --- a/tests/sendBeaconDispatcher.spec.ts +++ b/lib/event_processor/send_beacon_dispatcher.browser.spec.ts @@ -15,7 +15,7 @@ */ import { describe, beforeEach, it, expect, vi, MockInstance } from 'vitest'; -import sendBeaconDispatcher, { Event } from '../lib/plugins/event_dispatcher/send_beacon_dispatcher'; +import sendBeaconDispatcher, { Event } from './send_beacon_dispatcher.browser'; describe('dispatchEvent', function() { let sendBeaconSpy: MockInstance<typeof navigator.sendBeacon>; @@ -26,8 +26,8 @@ describe('dispatchEvent', function() { }); it('should call sendBeacon with correct url, data and type', async () => { - var eventParams = { testParam: 'testParamValue' }; - var eventObj: Event = { + const eventParams = { testParam: 'testParamValue' }; + const eventObj: Event = { url: 'https://cdn.com/event', httpVerb: 'POST', params: eventParams, diff --git a/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts b/lib/event_processor/send_beacon_dispatcher.browser.ts similarity index 93% rename from lib/plugins/event_dispatcher/send_beacon_dispatcher.ts rename to lib/event_processor/send_beacon_dispatcher.browser.ts index 1e8c04577..a2686b316 100644 --- a/lib/plugins/event_dispatcher/send_beacon_dispatcher.ts +++ b/lib/event_processor/send_beacon_dispatcher.browser.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { EventDispatcher, EventDispatcherResponse } from '../../event_processor/eventDispatcher'; +import { EventDispatcher, EventDispatcherResponse } from './event_dispatcher'; export type Event = { url: string; diff --git a/lib/event_processor/v1/buildEventV1.ts b/lib/event_processor/v1/buildEventV1.ts deleted file mode 100644 index 1232d52ec..000000000 --- a/lib/event_processor/v1/buildEventV1.ts +++ /dev/null @@ -1,272 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { EventTags, ConversionEvent, ImpressionEvent, VisitorAttribute } from '../events' -import { ProcessableEvent } from '../eventProcessor' -import { EventV1Request } from '../eventDispatcher' - -const ACTIVATE_EVENT_KEY = 'campaign_activated' -const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' -const BOT_FILTERING_KEY = '$opt_bot_filtering' - -export type EventV1 = { - account_id: string - project_id: string - revision: string - client_name: string - client_version: string - anonymize_ip: boolean - enrich_decisions: boolean - visitors: Visitor[] -} - -type Visitor = { - snapshots: Visitor.Snapshot[] - visitor_id: string - attributes: Visitor.Attribute[] -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -namespace Visitor { - type AttributeType = 'custom' - - export type Attribute = { - // attribute id - entity_id: string - // attribute key - key: string - type: AttributeType - value: string | number | boolean - } - - export type Snapshot = { - decisions?: Decision[] - events: SnapshotEvent[] - } - - type Decision = { - campaign_id: string | null - experiment_id: string | null - variation_id: string | null - metadata: Metadata - } - - type Metadata = { - flag_key: string; - rule_key: string; - rule_type: string; - variation_key: string; - enabled: boolean; - } - - export type SnapshotEvent = { - entity_id: string | null - timestamp: number - uuid: string - key: string - revenue?: number - value?: number - tags?: EventTags - } -} - - - -type Attributes = { - [key: string]: string | number | boolean -} - -/** - * Given an array of batchable Decision or ConversionEvent events it returns - * a single EventV1 with proper batching - * - * @param {ProcessableEvent[]} events - * @returns {EventV1} - */ -export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 { - const visitors: Visitor[] = [] - const data = events[0] - - events.forEach(event => { - if (event.type === 'conversion' || event.type === 'impression') { - const visitor = makeVisitor(event) - - if (event.type === 'impression') { - visitor.snapshots.push(makeDecisionSnapshot(event)) - } else if (event.type === 'conversion') { - visitor.snapshots.push(makeConversionSnapshot(event)) - } - - visitors.push(visitor) - } - }) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors, - } -} - -function makeConversionSnapshot(conversion: ConversionEvent): Visitor.Snapshot { - const tags: EventTags = { - ...conversion.tags, - } - - delete tags['revenue'] - delete tags['value'] - - const event: Visitor.SnapshotEvent = { - entity_id: conversion.event.id, - key: conversion.event.key, - timestamp: conversion.timestamp, - uuid: conversion.uuid, - } - - if (conversion.tags) { - event.tags = conversion.tags - } - - if (conversion.value != null) { - event.value = conversion.value - } - - if (conversion.revenue != null) { - event.revenue = conversion.revenue - } - - return { - events: [event], - } -} - -function makeDecisionSnapshot(event: ImpressionEvent): Visitor.Snapshot { - const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event - const layerId = layer ? layer.id : null - const experimentId = experiment?.id ?? '' - const variationId = variation?.id ?? '' - const variationKey = variation ? variation.key : '' - - return { - decisions: [ - { - campaign_id: layerId, - experiment_id: experimentId, - variation_id: variationId, - metadata: { - flag_key: flagKey, - rule_key: ruleKey, - rule_type: ruleType, - variation_key: variationKey, - enabled: enabled, - }, - }, - ], - events: [ - { - entity_id: layerId, - timestamp: event.timestamp, - key: ACTIVATE_EVENT_KEY, - uuid: event.uuid, - }, - ], - } -} - -function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { - const visitor: Visitor = { - snapshots: [], - visitor_id: data.user.id, - attributes: [], - } - - const type = 'custom' - data.user.attributes.forEach(attr => { - visitor.attributes.push({ - entity_id: attr.entityId, - key: attr.key, - type: type as 'custom', // tell the compiler this is always string "custom" - value: attr.value, - }) - }) - - if (typeof data.context.botFiltering === 'boolean') { - visitor.attributes.push({ - entity_id: BOT_FILTERING_KEY, - key: BOT_FILTERING_KEY, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: data.context.botFiltering, - }) - } - return visitor -} - -/** - * Event for usage with v1 logtier - * - * @export - * @interface EventBuilderV1 - */ - -export function buildImpressionEventV1(data: ImpressionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeDecisionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function buildConversionEventV1(data: ConversionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeConversionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function formatEvents(events: ProcessableEvent[]): EventV1Request { - return { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(events), - } -} diff --git a/lib/index.browser.ts b/lib/index.browser.ts index f7b7ba98c..5821d0aa0 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -19,7 +19,7 @@ import { getLogger, setErrorHandler, getErrorHandler, LogLevel } from './modules import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/default_dispatcher.browser'; -import sendBeaconEventDispatcher from './plugins/event_dispatcher/send_beacon_dispatcher'; +import sendBeaconEventDispatcher from './event_processor/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import { createNotificationCenter } from './core/notification_center'; diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index ba67811bd..076934eda 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -30,7 +30,6 @@ describe('optimizelyFactory', function() { assert.isDefined(optimizelyFactory.logging.createLogger); assert.isDefined(optimizelyFactory.logging.createNoOpLogger); assert.isDefined(optimizelyFactory.errorHandler); - assert.isDefined(optimizelyFactory.eventDispatcher); assert.isDefined(optimizelyFactory.enums); }); diff --git a/lib/index.lite.ts b/lib/index.lite.ts index b7fb41def..5aec89ecb 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -23,7 +23,6 @@ } from './modules/logging'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; -import noOpEventDispatcher from './plugins/event_dispatcher/no_op'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import Optimizely from './optimizely'; @@ -89,7 +88,6 @@ setLogLevel(LogLevel.ERROR); export { loggerPlugin as logging, defaultErrorHandler as errorHandler, - noOpEventDispatcher as eventDispatcher, enums, setLogHandler as setLogger, setLogLevel, @@ -103,7 +101,6 @@ export default { ...commonExports, logging: loggerPlugin, errorHandler: defaultErrorHandler, - eventDispatcher: noOpEventDispatcher, enums, setLogger: setLogHandler, setLogLevel, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index a15a7711f..c2d247b1d 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -17,7 +17,7 @@ import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../core/notification_center'; -import { EventProcessor } from '../event_processor/eventProcessor'; +import { EventProcessor } from '../event_processor/event_processor'; import { IOdpManager } from '../core/odp/odp_manager'; import { OdpEvent } from '../core/odp/odp_event'; @@ -41,8 +41,8 @@ import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; import { ProjectConfigManager } from '../project_config/project_config_manager'; import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; -import { getImpressionEvent, getConversionEvent } from '../core/event_builder'; -import { buildImpressionEvent, buildConversionEvent } from '../core/event_builder/event_helpers'; +import { getImpressionEvent, getConversionEvent } from '../event_processor/event_builder'; +import { buildImpressionEvent, buildConversionEvent } from '../event_processor/event_builder/event_helpers'; import fns from '../utils/fns'; import { validate } from '../utils/attributes_validator'; import * as eventTagsValidator from '../utils/event_tags_validator'; diff --git a/lib/plugins/event_dispatcher/no_op.ts b/lib/plugins/event_dispatcher/no_op.ts deleted file mode 100644 index cbe2473d7..000000000 --- a/lib/plugins/event_dispatcher/no_op.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2021, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Event } from '../../shared_types'; - -/** - * No Op Event dispatcher for non standard platforms like edge workers etc - * @param {Event} eventObj - * @param {Function} callback - */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -export const dispatchEvent = function( - eventObj: Event, -): any { - // NoOp Event dispatcher. It does nothing really. -} - -export default { - dispatchEvent, -}; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index b5249266f..1b26d51ad 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -38,11 +38,11 @@ import { IUserAgentParser } from './core/odp/user_agent_parser'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; import { ProjectConfig } from './project_config/project_config'; import { ProjectConfigManager } from './project_config/project_config_manager'; -import { EventDispatcher } from './event_processor/eventDispatcher'; -import { EventProcessor } from './event_processor/eventProcessor'; +import { EventDispatcher } from './event_processor/event_dispatcher'; +import { EventProcessor } from './event_processor/event_processor'; -export { EventDispatcher } from './event_processor/eventDispatcher'; -export { EventProcessor } from './event_processor/eventProcessor'; +export { EventDispatcher } from './event_processor/event_dispatcher'; +export { EventProcessor } from './event_processor/event_processor'; export interface BucketerParams { experimentId: string; experimentKey: string; diff --git a/tests/buildEventV1.spec.ts b/tests/buildEventV1.spec.ts deleted file mode 100644 index dafa67e60..000000000 --- a/tests/buildEventV1.spec.ts +++ /dev/null @@ -1,812 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, it, expect } from 'vitest'; - -import { - buildConversionEventV1, - buildImpressionEventV1, - makeBatchedEventV1, -} from '../lib/event_processor/v1/buildEventV1' -import { ImpressionEvent, ConversionEvent } from '../lib/event_processor/events' - -describe('buildEventV1', () => { - describe('buildImpressionEventV1', () => { - it('should build an ImpressionEventV1 when experiment and variation are defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build an ImpressionEventV1 when experiment and variation are not defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: null, - }, - - experiment: { - id: null, - key: '', - }, - - variation: { - id: null, - key: '', - }, - - ruleKey: '', - flagKey: 'flagKey1', - ruleType: 'rollout', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: null, - experiment_id: "", - variation_id: "", - metadata: { - flag_key: 'flagKey1', - rule_key: '', - rule_type: 'rollout', - variation_key: '', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: null, - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) - - describe('buildConversionEventV1', () => { - it('should build a ConversionEventV1 when tags object is defined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when tags object is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when event id is null', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: null, - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: null, - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should include revenue and value if they are 0', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - - revenue: 0, - value: 0, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - revenue: 0, - value: 0, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should not include $opt_bot_filtering attribute if context.botFiltering is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - ], - }, - ], - }) - }) - }) - - describe('makeBatchedEventV1', () => { - it('should batch Conversion and Impression events together', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = makeBatchedEventV1([impressionEvent, conversionEvent]) - - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) -}) diff --git a/tsconfig.spec.json b/tsconfig.spec.json index d27f5db0d..f61c713df 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -6,9 +6,21 @@ ], "typeRoots": [ "./node_modules/@types" - ] + ], + "target": "ESNext" }, + "exclude": [ + "./dist", + "./lib/**/*.tests.js", + "./lib/**/*.tests.ts", + "./lib/**/*.umdtests.js", + "node_modules" + ], "include": [ + "./lib/**/*.ts", + "./lib/**/*.js", + "./lib/modules/**/*.ts", + "./lib/modules/**/**/*.ts", "tests/**/*.ts", "**/*.spec.ts" ] From 77706643f20eae1352afd7b1e1effe47f7f4cb92 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:12:17 +0600 Subject: [PATCH 096/200] [FSSDK-10934] Cherry picking commits from 5.x.x to master (#968) --- lib/core/decision_service/index.tests.js | 22 +- lib/core/decision_service/index.ts | 215 +++++++++++------ lib/optimizely/index.tests.js | 144 ++++++++++++ lib/optimizely/index.ts | 256 ++++++++++++--------- lib/optimizely_user_context/index.tests.js | 12 +- lib/project_config/project_config.ts | 1 + lib/shared_types.ts | 1 + lib/tests/test_data.ts | 5 + lib/utils/enums/index.ts | 3 +- 9 files changed, 475 insertions(+), 184 deletions(-) diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 025a11d69..9ce0337e3 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -381,7 +381,7 @@ describe('lib/core/decision_service', function() { ); assert.strictEqual( buildLogMessageFromArgs(mockLogger.log.args[4]), - 'DECISION_SERVICE: Saved variation "control" of experiment "testExperiment" for user "decision_service_user".' + 'DECISION_SERVICE: Saved user profile for user "decision_service_user".' ); }); @@ -394,6 +394,7 @@ describe('lib/core/decision_service', function() { optimizely: {}, userId: 'decision_service_user', }); + assert.strictEqual( 'control', decisionServiceInstance.getVariation(configObj, experiment, user).result @@ -402,11 +403,11 @@ describe('lib/core/decision_service', function() { sinon.assert.calledOnce(bucketerStub); // should still go through with bucketing assert.strictEqual( buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' + 'DECISION_SERVICE: Error while looking up user profile for user ID "decision_service_user": I am an error.' ); assert.strictEqual( buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Error while looking up user profile for user ID "decision_service_user": I am an error.' + 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' ); }); @@ -1281,7 +1282,7 @@ describe('lib/core/decision_service', function() { reasons: [], }; experiment = configObj.experimentIdMap['594098']; - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponse); getVariationStub.withArgs(configObj, experiment, user).returns(fakeDecisionResponseWithArgs); }); @@ -1497,12 +1498,11 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - sinon.assert.calledWithExactly( + sinon.assert.calledWith( getVariationStub, configObj, experiment, user, - {} ); }); }); @@ -1515,7 +1515,7 @@ describe('lib/core/decision_service', function() { optimizely: {}, userId: 'user1', }); - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponse); }); @@ -1554,7 +1554,7 @@ describe('lib/core/decision_service', function() { result: 'var', reasons: [], }; - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponseWithArgs); getVariationStub.withArgs(configObj, 'exp_with_group', user).returns(fakeDecisionResponseWithArgs); }); @@ -1611,7 +1611,7 @@ describe('lib/core/decision_service', function() { optimizely: {}, userId: 'user1', }); - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponse); }); @@ -1679,6 +1679,7 @@ describe('lib/core/decision_service', function() { status: 'Not started', key: '594031', id: '594031', + isRollout: true, variations: [ { id: '594032', @@ -1816,6 +1817,7 @@ describe('lib/core/decision_service', function() { status: 'Not started', key: '594037', id: '594037', + isRollout: true, variations: [ { id: '594038', @@ -2000,6 +2002,7 @@ describe('lib/core/decision_service', function() { status: 'Not started', key: '594037', id: '594037', + isRollout: true, variations: [ { id: '594038', @@ -2154,6 +2157,7 @@ describe('lib/core/decision_service', function() { layerId: '599055', forcedVariations: {}, audienceIds: [], + isRollout: true, variations: [ { key: '599057', diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index c3fea53eb..16718fe7e 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -55,7 +55,7 @@ import { Variation, } from '../../shared_types'; -const MODULE_NAME = 'DECISION_SERVICE'; +export const MODULE_NAME = 'DECISION_SERVICE'; export interface DecisionObj { experiment: Experiment | null; @@ -73,6 +73,11 @@ interface DeliveryRuleResponse<T, K> extends DecisionResponse<T> { skipToEveryoneElse: K; } +interface UserProfileTracker { + userProfile: ExperimentBucketMap | null; + isProfileUpdated: boolean; +} + /** * Optimizely's decision service that determines which variation of an experiment the user will be allocated to. * @@ -102,20 +107,21 @@ export class DecisionService { } /** - * Gets variation where visitor will be bucketed. - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {Experiment} experiment - * @param {OptimizelyUserContext} user A user context - * @param {[key: string]: boolean} options Optional map of decide options - * @return {DecisionResponse<string|null>} DecisionResponse containing the variation the user is bucketed into - * and the decide reasons. + * Resolves the variation into which the visitor will be bucketed. + * + * @param {ProjectConfig} configObj - The parsed project configuration object. + * @param {Experiment} experiment - The experiment for which the variation is being resolved. + * @param {OptimizelyUserContext} user - The user context associated with this decision. + * @returns {DecisionResponse<string|null>} - A DecisionResponse containing the variation the user is bucketed into, + * along with the decision reasons. */ - getVariation( + private resolveVariation( configObj: ProjectConfig, experiment: Experiment, user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} - ): DecisionResponse<string | null> { + shouldIgnoreUPS: boolean, + userProfileTracker: UserProfileTracker + ): DecisionResponse<string | null> { const userId = user.getUserId(); const attributes = user.getAttributes(); // by default, the bucketing ID should be the user ID @@ -150,12 +156,10 @@ export class DecisionService { }; } - const shouldIgnoreUPS = options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; - const experimentBucketMap = this.resolveExperimentBucketMap(userId, attributes); // check for sticky bucketing if decide options do not include shouldIgnoreUPS if (!shouldIgnoreUPS) { - variation = this.getStoredVariation(configObj, experiment, userId, experimentBucketMap); + variation = this.getStoredVariation(configObj, experiment, userId, userProfileTracker.userProfile); if (variation) { this.logger.log( LOG_LEVEL.INFO, @@ -252,7 +256,7 @@ export class DecisionService { ]); // persist bucketing if decide options do not include shouldIgnoreUPS if (!shouldIgnoreUPS) { - this.saveUserProfile(experiment, variation, userId, experimentBucketMap); + this.updateUserProfile(experiment, variation, userProfileTracker); } return { @@ -261,6 +265,39 @@ export class DecisionService { }; } + /** + * Gets variation where visitor will be bucketed. + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {Experiment} experiment + * @param {OptimizelyUserContext} user A user context + * @param {[key: string]: boolean} options Optional map of decide options + * @return {DecisionResponse<string|null>} DecisionResponse containing the variation the user is bucketed into + * and the decide reasons. + */ + getVariation( + configObj: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext, + options: { [key: string]: boolean } = {} + ): DecisionResponse<string | null> { + const shouldIgnoreUPS = options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; + const userProfileTracker: UserProfileTracker = { + isProfileUpdated: false, + userProfile: null, + } + if(!shouldIgnoreUPS) { + userProfileTracker.userProfile = this.resolveExperimentBucketMap(user.getUserId(), user.getAttributes()); + } + + const result = this.resolveVariation(configObj, experiment, user, shouldIgnoreUPS, userProfileTracker); + + if(!shouldIgnoreUPS) { + this.saveUserProfile(user.getUserId(), userProfileTracker) + } + + return result + } + /** * Merges attributes from attributes[STICKY_BUCKETING_KEY] and userProfileService * @param {string} userId @@ -446,9 +483,9 @@ export class DecisionService { configObj: ProjectConfig, experiment: Experiment, userId: string, - experimentBucketMap: ExperimentBucketMap + experimentBucketMap: ExperimentBucketMap | null ): Variation | null { - if (experimentBucketMap.hasOwnProperty(experiment.id)) { + if (experimentBucketMap?.hasOwnProperty(experiment.id)) { const decision = experimentBucketMap[experiment.id]; const variationId = decision.variation_id; if (configObj.variationIdMap.hasOwnProperty(variationId)) { @@ -497,6 +534,21 @@ export class DecisionService { return null; } + private updateUserProfile( + experiment: Experiment, + variation: Variation, + userProfileTracker: UserProfileTracker + ): void { + if(!userProfileTracker.userProfile) { + return + } + + userProfileTracker.userProfile[experiment.id] = { + variation_id: variation.id + } + userProfileTracker.isProfileUpdated = true + } + /** * Saves the bucketing decision to the user profile * @param {Experiment} experiment @@ -505,31 +557,25 @@ export class DecisionService { * @param {ExperimentBucketMap} experimentBucketMap */ private saveUserProfile( - experiment: Experiment, - variation: Variation, userId: string, - experimentBucketMap: ExperimentBucketMap + userProfileTracker: UserProfileTracker ): void { - if (!this.userProfileService) { + const { userProfile, isProfileUpdated } = userProfileTracker; + + if (!this.userProfileService || !userProfile || !isProfileUpdated) { return; } try { - experimentBucketMap[experiment.id] = { - variation_id: variation.id - }; - this.userProfileService.save({ user_id: userId, - experiment_bucket_map: experimentBucketMap, + experiment_bucket_map: userProfile, }); this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.SAVED_VARIATION, + LOG_MESSAGES.SAVED_USER_VARIATION, MODULE_NAME, - variation.key, - experiment.key, userId, ); } catch (ex: any) { @@ -537,6 +583,74 @@ export class DecisionService { } } + /** + * Determines variations for the specified feature flags. + * + * @param {ProjectConfig} configObj - The parsed project configuration object. + * @param {FeatureFlag[]} featureFlags - The feature flags for which variations are to be determined. + * @param {OptimizelyUserContext} user - The user context associated with this decision. + * @param {Record<string, boolean>} options - An optional map of decision options. + * @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with + * experiment, variation, decisionSource properties, and decision reasons. + */ + getVariationsForFeatureList(configObj: ProjectConfig, + featureFlags: FeatureFlag[], + user: OptimizelyUserContext, + options: { [key: string]: boolean } = {}): DecisionResponse<DecisionObj>[] { + const userId = user.getUserId(); + const attributes = user.getAttributes(); + const decisions: DecisionResponse<DecisionObj>[] = []; + const userProfileTracker : UserProfileTracker = { + isProfileUpdated: false, + userProfile: null, + } + const shouldIgnoreUPS = options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; + + if(!shouldIgnoreUPS) { + userProfileTracker.userProfile = this.resolveExperimentBucketMap(userId, attributes); + } + + for(const feature of featureFlags) { + const decideReasons: (string | number)[][] = []; + const decisionVariation = this.getVariationForFeatureExperiment(configObj, feature, user, shouldIgnoreUPS, userProfileTracker); + decideReasons.push(...decisionVariation.reasons); + const experimentDecision = decisionVariation.result; + + if (experimentDecision.variation !== null) { + decisions.push({ + result: experimentDecision, + reasons: decideReasons, + }); + continue; + } + + const decisionRolloutVariation = this.getVariationForRollout(configObj, feature, user); + decideReasons.push(...decisionRolloutVariation.reasons); + const rolloutDecision = decisionRolloutVariation.result; + const userId = user.getUserId(); + + if (rolloutDecision.variation) { + this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key); + decideReasons.push([LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + } else { + this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key); + decideReasons.push([LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + } + + decisions.push({ + result: rolloutDecision, + reasons: decideReasons, + }); + } + + if(!shouldIgnoreUPS) { + this.saveUserProfile(userId, userProfileTracker) + } + + return decisions + + } + /** * Given a feature, user ID, and attributes, returns a decision response containing * an object representing a decision and decide reasons. If the user was bucketed into @@ -558,45 +672,15 @@ export class DecisionService { user: OptimizelyUserContext, options: { [key: string]: boolean } = {} ): DecisionResponse<DecisionObj> { - - const decideReasons: (string | number)[][] = []; - const decisionVariation = this.getVariationForFeatureExperiment(configObj, feature, user, options); - decideReasons.push(...decisionVariation.reasons); - const experimentDecision = decisionVariation.result; - - if (experimentDecision.variation !== null) { - return { - result: experimentDecision, - reasons: decideReasons, - }; - } - - const decisionRolloutVariation = this.getVariationForRollout(configObj, feature, user); - decideReasons.push(...decisionRolloutVariation.reasons); - const rolloutDecision = decisionRolloutVariation.result; - const userId = user.getUserId(); - if (rolloutDecision.variation) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key); - decideReasons.push([LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); - return { - result: rolloutDecision, - reasons: decideReasons, - }; - } - - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key); - decideReasons.push([LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); - return { - result: rolloutDecision, - reasons: decideReasons, - }; + return this.getVariationsForFeatureList(configObj, [feature], user, options)[0] } private getVariationForFeatureExperiment( configObj: ProjectConfig, feature: FeatureFlag, user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} + shouldIgnoreUPS: boolean, + userProfileTracker: UserProfileTracker ): DecisionResponse<DecisionObj> { const decideReasons: (string | number)[][] = []; @@ -611,7 +695,7 @@ export class DecisionService { for (index = 0; index < feature.experimentIds.length; index++) { const experiment = getExperimentFromId(configObj, feature.experimentIds[index], this.logger); if (experiment) { - decisionVariation = this.getVariationFromExperimentRule(configObj, feature.key, experiment, user, options); + decisionVariation = this.getVariationFromExperimentRule(configObj, feature.key, experiment, user, shouldIgnoreUPS, userProfileTracker); decideReasons.push(...decisionVariation.reasons); variationKey = decisionVariation.result; if (variationKey) { @@ -1108,7 +1192,8 @@ export class DecisionService { flagKey: string, rule: Experiment, user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} + shouldIgnoreUPS: boolean, + userProfileTracker: UserProfileTracker ): DecisionResponse<string | null> { const decideReasons: (string | number)[][] = []; @@ -1123,7 +1208,7 @@ export class DecisionService { reasons: decideReasons, }; } - const decisionVariation = this.getVariation(configObj, rule, user, options); + const decisionVariation = this.resolveVariation(configObj, rule, user, shouldIgnoreUPS, userProfileTracker); decideReasons.push(...decisionVariation.reasons); const variationKey = decisionVariation.result; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index f0dd8e00e..a66840215 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -5872,6 +5872,84 @@ describe('lib/optimizely', function() { assert.deepEqual(decision, expectedDecision); sinon.assert.calledTwice(eventDispatcher.dispatchEvent); }); + describe('UPS Batching', function() { + var userProfileServiceInstance = { + lookup: function() {}, + save: function() {}, + }; + beforeEach(function() { + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }), + userProfileService: userProfileServiceInstance, + errorHandler: errorHandler, + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: [], + notificationCenter, + eventProcessor, + }); + + sinon.stub(optlyInstance.decisionService.userProfileService, 'lookup') + sinon.stub(optlyInstance.decisionService.userProfileService, 'save') + // + }); + + it('Should call UPS methods only once', function() { + var flagKeysArray = ['feature_1', 'feature_2']; + var user = optlyInstance.createUserContext(userId); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKeysArray[0], userId); + var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKeysArray[1], userId); + optlyInstance.decisionService.userProfileService.save.resetHistory(); + optlyInstance.decisionService.userProfileService.lookup.resetHistory(); + var decisionsMap = optlyInstance.decideForKeys(user, flagKeysArray); + var decision1 = decisionsMap[flagKeysArray[0]]; + var decision2 = decisionsMap[flagKeysArray[1]]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: expectedVariables1, + ruleKey: '18322080788', + flagKey: flagKeysArray[0], + userContext: user, + reasons: [], + }; + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables2, + ruleKey: 'exp_no_audience', + flagKey: flagKeysArray[1], + userContext: user, + reasons: [], + }; + var userProfile = { + user_id: userId, + experiment_bucket_map: { + '10420810910': { // ruleKey from expectedDecision1 + variation_id: '10418551353' // variationKey from expectedDecision1 + } + } + }; + + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + // UPS batch assertion + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.lookup); + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.save); + + // UPS save assertion + sinon.assert.calledWithExactly(optlyInstance.decisionService.userProfileService.save, userProfile); + }); + }) + }); describe('#decideAll', function() { @@ -6035,6 +6113,72 @@ describe('lib/optimizely', function() { sinon.assert.calledThrice(eventDispatcher.dispatchEvent); }); }); + + describe('UPS batching', function() { + beforeEach(function() { + var userProfileServiceInstance = { + lookup: function() {}, + save: function() {}, + }; + + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }), + userProfileService: userProfileServiceInstance, + errorHandler: errorHandler, + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY], + eventProcessor, + notificationCenter, + }); + + sinon.stub(optlyInstance.decisionService.userProfileService, 'lookup') + sinon.stub(optlyInstance.decisionService.userProfileService, 'save') + }); + + it('should call UPS methods only once', function() { + var flagKey1 = 'feature_1'; + var flagKey2 = 'feature_2'; + var user = optlyInstance.createUserContext(userId, { gender: 'female' }); + var decisionsMap = optlyInstance.decideAll(user, [OptimizelyDecideOption.EXCLUDE_VARIABLES]); + var decision1 = decisionsMap[flagKey1]; + var decision2 = decisionsMap[flagKey2]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: user, + reasons: [], + }; + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: user, + reasons: [], + }; + + // Decision assertion + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + + // UPS batch assertion + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.lookup); + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.save); + }) + }); }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index c2d247b1d..8122c50e7 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -79,6 +79,8 @@ type InputKey = 'feature_key' | 'user_id' | 'variable_key' | 'experiment_key' | type StringInputs = Partial<Record<InputKey, unknown>>; +type DecisionReasons = (string | number)[]; + export default class Optimizely implements Client { private isOptimizelyConfigValid: boolean; private disposeOnUpdate?: Fn; @@ -489,7 +491,7 @@ export default class Optimizely implements Client { } const experiment = configObj.experimentKeyMap[experimentKey]; - if (!experiment) { + if (!experiment || experiment.isRollout) { this.logger.log(LOG_LEVEL.DEBUG, ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey); return null; } @@ -1476,105 +1478,14 @@ export default class Optimizely implements Client { } decide(user: OptimizelyUserContext, key: string, options: OptimizelyDecideOption[] = []): OptimizelyDecision { - const userId = user.getUserId(); - const attributes = user.getAttributes(); const configObj = this.projectConfigManager.getConfig(); - const reasons: (string | number)[][] = []; - let decisionObj: DecisionObj; + if (!this.isValidInstance() || !configObj) { this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decide'); return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); } - const feature = configObj.featureKeyMap[key]; - if (!feature) { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key); - return newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); - } - - const allDecideOptions = this.getAllDecideOptions(options); - - const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key); - reasons.push(...forcedDecisionResponse.reasons); - const variation = forcedDecisionResponse.result; - if (variation) { - decisionObj = { - experiment: null, - variation: variation, - decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; - } else { - const decisionVariation = this.decisionService.getVariationForFeature(configObj, feature, user, allDecideOptions); - reasons.push(...decisionVariation.reasons); - decisionObj = decisionVariation.result; - } - const decisionSource = decisionObj.decisionSource; - const experimentKey = decisionObj.experiment?.key ?? null; - const variationKey = decisionObj.variation?.key ?? null; - const flagEnabled: boolean = decision.getFeatureEnabledFromVariation(decisionObj); - if (flagEnabled === true) { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, key, userId); - } else { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, key, userId); - } - - const variablesMap: { [key: string]: unknown } = {}; - let decisionEventDispatched = false; - - if (!allDecideOptions[OptimizelyDecideOption.EXCLUDE_VARIABLES]) { - feature.variables.forEach(variable => { - variablesMap[variable.key] = this.getFeatureVariableValueFromVariation( - key, - flagEnabled, - decisionObj.variation, - variable, - userId - ); - }); - } - - if ( - !allDecideOptions[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && - (decisionSource === DECISION_SOURCES.FEATURE_TEST || - (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj))) - ) { - this.sendImpressionEvent(decisionObj, key, userId, flagEnabled, attributes); - decisionEventDispatched = true; - } - - const shouldIncludeReasons = allDecideOptions[OptimizelyDecideOption.INCLUDE_REASONS]; - - let reportedReasons: string[] = []; - if (shouldIncludeReasons) { - reportedReasons = reasons.map(reason => sprintf(reason[0] as string, ...reason.slice(1))); - } - - const featureInfo = { - flagKey: key, - enabled: flagEnabled, - variationKey: variationKey, - ruleKey: experimentKey, - variables: variablesMap, - reasons: reportedReasons, - decisionEventDispatched: decisionEventDispatched, - }; - - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { - type: DECISION_NOTIFICATION_TYPES.FLAG, - userId: userId, - attributes: attributes, - decisionInfo: featureInfo, - }); - - return { - variationKey: variationKey, - enabled: flagEnabled, - variables: variablesMap, - ruleKey: experimentKey, - flagKey: key, - userContext: user, - reasons: reportedReasons, - }; + return this.decideForKeys(user, [key], options, true)[key]; } /** @@ -1600,6 +1511,98 @@ export default class Optimizely implements Client { return allDecideOptions; } + /** + * Makes a decision for a given feature key. + * + * @param {OptimizelyUserContext} user - The user context associated with this Optimizely client. + * @param {string} key - The feature key for which a decision will be made. + * @param {DecisionObj} decisionObj - The decision object containing decision details. + * @param {DecisionReasons[]} reasons - An array of reasons for the decision. + * @param {Record<string, boolean>} options - A map of options for decision-making. + * @param {projectConfig.ProjectConfig} configObj - The project configuration object. + * @returns {OptimizelyDecision} - The decision object for the feature flag. + */ + private generateDecision( + user: OptimizelyUserContext, + key: string, + decisionObj: DecisionObj, + reasons: DecisionReasons[], + options: Record<string, boolean>, + configObj: projectConfig.ProjectConfig, + ): OptimizelyDecision { + const userId = user.getUserId() + const attributes = user.getAttributes() + const feature = configObj.featureKeyMap[key] + const decisionSource = decisionObj.decisionSource; + const experimentKey = decisionObj.experiment?.key ?? null; + const variationKey = decisionObj.variation?.key ?? null; + const flagEnabled: boolean = decision.getFeatureEnabledFromVariation(decisionObj); + const variablesMap: { [key: string]: unknown } = {}; + let decisionEventDispatched = false; + + if (flagEnabled) { + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, key, userId); + } else { + this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, key, userId); + } + + + if (!options[OptimizelyDecideOption.EXCLUDE_VARIABLES]) { + feature.variables.forEach(variable => { + variablesMap[variable.key] = this.getFeatureVariableValueFromVariation( + key, + flagEnabled, + decisionObj.variation, + variable, + userId + ); + }); + } + + if ( + !options[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && + (decisionSource === DECISION_SOURCES.FEATURE_TEST || + (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj))) + ) { + this.sendImpressionEvent(decisionObj, key, userId, flagEnabled, attributes); + decisionEventDispatched = true; + } + + const shouldIncludeReasons = options[OptimizelyDecideOption.INCLUDE_REASONS]; + + let reportedReasons: string[] = []; + if (shouldIncludeReasons) { + reportedReasons = reasons.map(reason => sprintf(reason[0] as string, ...reason.slice(1))); + } + + const featureInfo = { + flagKey: key, + enabled: flagEnabled, + variationKey: variationKey, + ruleKey: experimentKey, + variables: variablesMap, + reasons: reportedReasons, + decisionEventDispatched: decisionEventDispatched, + }; + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: userId, + attributes: attributes, + decisionInfo: featureInfo, + }); + + return { + variationKey: variationKey, + enabled: flagEnabled, + variables: variablesMap, + ruleKey: experimentKey, + flagKey: key, + userContext: user, + reasons: reportedReasons, + }; + } + /** * Returns an object of decision results for multiple flag keys and a user context. * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. @@ -1612,10 +1615,18 @@ export default class Optimizely implements Client { decideForKeys( user: OptimizelyUserContext, keys: string[], - options: OptimizelyDecideOption[] = [] - ): { [key: string]: OptimizelyDecision } { - const decisionMap: { [key: string]: OptimizelyDecision } = {}; - if (!this.isValidInstance()) { + options: OptimizelyDecideOption[] = [], + ignoreEnabledFlagOption?:boolean + ): Record<string, OptimizelyDecision> { + const decisionMap: Record<string, OptimizelyDecision> = {}; + const flagDecisions: Record<string, DecisionObj> = {}; + const decisionReasonsMap: Record<string, DecisionReasons[]> = {}; + const flagsWithoutForcedDecision = []; + const validKeys = []; + + const configObj = this.projectConfigManager.getConfig() + + if (!this.isValidInstance() || !configObj) { this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys'); return decisionMap; } @@ -1624,12 +1635,51 @@ export default class Optimizely implements Client { } const allDecideOptions = this.getAllDecideOptions(options); - keys.forEach(key => { - const optimizelyDecision: OptimizelyDecision = this.decide(user, key, options); - if (!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || optimizelyDecision.enabled) { - decisionMap[key] = optimizelyDecision; + + if (ignoreEnabledFlagOption) { + delete allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY]; + } + + for(const key of keys) { + const feature = configObj.featureKeyMap[key]; + if (!feature) { + this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key); + decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); + continue } - }); + + validKeys.push(key); + const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key); + decisionReasonsMap[key] = forcedDecisionResponse.reasons + const variation = forcedDecisionResponse.result; + + if (variation) { + flagDecisions[key] = { + experiment: null, + variation: variation, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }; + } else { + flagsWithoutForcedDecision.push(feature) + } + } + + const decisionList = this.decisionService.getVariationsForFeatureList(configObj, flagsWithoutForcedDecision, user, allDecideOptions); + + for(let i = 0; i < flagsWithoutForcedDecision.length; i++) { + const key = flagsWithoutForcedDecision[i].key; + const decision = decisionList[i]; + flagDecisions[key] = decision.result; + decisionReasonsMap[key] = [...decisionReasonsMap[key], ...decision.reasons]; + } + + for(const validKey of validKeys) { + const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj); + + if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) { + decisionMap[validKey] = decision; + } + } return decisionMap; } diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 0d7a66f2a..0e169fa7b 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -42,6 +42,7 @@ const getMockEventDispatcher = () => { } const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { + const createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(datafileObj), }); @@ -49,7 +50,6 @@ const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { const eventProcessor = getForwardingEventProcessor(eventDispatcher); const notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); const optlyInstance = new Optimizely({ clientEngine: 'node-sdk', @@ -72,7 +72,7 @@ describe('lib/optimizely_user_context', function() { describe('APIs', function() { var fakeOptimizely; var userId = 'tester'; - var options = 'fakeOption'; + var options = ['fakeOption']; describe('#setAttribute', function() { fakeOptimizely = { decide: sinon.stub().returns({}), @@ -328,12 +328,12 @@ describe('lib/optimizely_user_context', function() { }); describe('#setForcedDecision', function() { - var createdLogger = createLogger({ + let createdLogger = createLogger({ logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); var stubLogHandler; - let optlyInstance, notificationCenter, createdLogger, eventDispatcher; + let optlyInstance, notificationCenter, eventDispatcher; beforeEach(function() { stubLogHandler = { @@ -840,7 +840,7 @@ describe('lib/optimizely_user_context', function() { describe('when forced decision is set for a flag and an experiment rule', function() { var optlyInstance; - var createdLogger = createLogger({ + const createdLogger = createLogger({ logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); @@ -888,7 +888,7 @@ describe('lib/optimizely_user_context', function() { describe('#getForcedDecision', function() { it('should return correct forced variation', function() { - var createdLogger = createLogger({ + const createdLogger = createLogger({ logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index a31dabe5e..314372a87 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -166,6 +166,7 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.rolloutIdMap = keyBy(projectConfig.rollouts || [], 'id'); objectValues(projectConfig.rolloutIdMap || {}).forEach(rollout => { (rollout.experiments || []).forEach(experiment => { + experiment.isRollout = true projectConfig.experiments.push(experiment); // Creates { <variationKey>: <variation> } map inside of the experiment experiment.variationKeyMap = keyBy(experiment.variations, 'key'); diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 1b26d51ad..eb99d4578 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -171,6 +171,7 @@ export interface Experiment { audienceIds: string[]; trafficAllocation: TrafficAllocation[]; forcedVariations?: { [key: string]: string }; + isRollout?: boolean; } export enum VariableType { diff --git a/lib/tests/test_data.ts b/lib/tests/test_data.ts index 76d822eb8..d792188fa 100644 --- a/lib/tests/test_data.ts +++ b/lib/tests/test_data.ts @@ -1649,6 +1649,7 @@ export var datafileWithFeaturesExpectedData = { status: 'Not started', key: '599056', id: '599056', + isRollout: true, variationKeyMap: { 599057: { key: '599057', @@ -1713,6 +1714,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594031', id: '594031', + isRollout: true, variationKeyMap: { 594032: { variables: [ @@ -1785,6 +1787,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594037', id: '594037', + isRollout: true, variationKeyMap: { 594038: { variables: [ @@ -1858,6 +1861,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594060', id: '594060', + isRollout: true, variationKeyMap: { 594061: { variables: [ @@ -1922,6 +1926,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594066', id: '594066', + isRollout: true, variationKeyMap: { 594067: { variables: [ diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 78fd6f907..db8575729 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -129,7 +129,8 @@ export const LOG_MESSAGES = { RETURNING_STORED_VARIATION: '%s: Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.', ROLLOUT_HAS_NO_EXPERIMENTS: '%s: Rollout of feature %s has no experiments', - SAVED_VARIATION: '%s: Saved variation "%s" of experiment "%s" for user "%s".', + SAVED_USER_VARIATION: '%s: Saved user profile for user "%s".', + UPDATED_USER_VARIATION: '%s: Updated variation "%s" of experiment "%s" for user "%s".', SAVED_VARIATION_NOT_FOUND: '%s: User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.', SHOULD_NOT_DISPATCH_ACTIVATE: '%s: Experiment %s is not in "Running" state. Not activating user.', From 8d013739033cd766ff96b39513a282140385b222 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 26 Nov 2024 19:18:06 +0600 Subject: [PATCH 097/200] [FSSDK-10941] event processor files and directories cleanup - part 2 (#967) --- ...batch_event_processor.react_native.spec.ts | 4 +- .../batch_event_processor.spec.ts | 58 +- lib/event_processor/batch_event_processor.ts | 20 +- .../default_dispatcher.spec.ts | 4 +- lib/event_processor/default_dispatcher.ts | 4 +- .../event_builder/index.tests.js | 1708 ----------------- lib/event_processor/event_builder/index.ts | 322 ---- ...ild_event_v1.spec.ts => log_event.spec.ts} | 10 +- .../{build_event_v1.ts => log_event.ts} | 23 +- ...t_helpers.tests.js => user_event.tests.js} | 4 +- .../{event_helpers.ts => user_event.ts} | 146 +- lib/event_processor/event_dispatcher.ts | 8 +- lib/event_processor/event_processor.ts | 6 +- lib/event_processor/events.ts | 101 - .../forwarding_event_processor.spec.ts | 12 +- .../forwarding_event_processor.ts | 10 +- lib/optimizely/index.tests.js | 1 - lib/optimizely/index.ts | 122 +- lib/utils/event_tag_utils/index.ts | 2 +- lib/utils/fns/index.ts | 2 +- 20 files changed, 185 insertions(+), 2382 deletions(-) delete mode 100644 lib/event_processor/event_builder/index.tests.js delete mode 100644 lib/event_processor/event_builder/index.ts rename lib/event_processor/event_builder/{build_event_v1.spec.ts => log_event.spec.ts} (98%) rename lib/event_processor/event_builder/{build_event_v1.ts => log_event.ts} (93%) rename lib/event_processor/event_builder/{event_helpers.tests.js => user_event.tests.js} (98%) rename lib/event_processor/event_builder/{event_helpers.ts => user_event.ts} (78%) delete mode 100644 lib/event_processor/events.ts diff --git a/lib/event_processor/batch_event_processor.react_native.spec.ts b/lib/event_processor/batch_event_processor.react_native.spec.ts index ea1612f4c..b2b50fe0f 100644 --- a/lib/event_processor/batch_event_processor.react_native.spec.ts +++ b/lib/event_processor/batch_event_processor.react_native.spec.ts @@ -50,7 +50,7 @@ import { getMockRepeater } from '../tests/mock/mock_repeater'; import { getMockAsyncCache } from '../tests/mock/mock_cache'; import { EventWithId } from './batch_event_processor'; -import { formatEvents } from './event_builder/build_event_v1'; +import { buildLogEvent } from './event_builder/build_event_v1'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ProcessableEvent } from './event_processor'; @@ -138,7 +138,7 @@ describe('ReactNativeNetInfoEventProcessor', () => { await exhaustMicrotasks(); - expect(eventDispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents(events)); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); }); it('should unsubscribe from netinfo listener when stopped', async () => { diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index 2c81f9215..61318b92c 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -19,7 +19,7 @@ import { EventWithId, BatchEventProcessor } from './batch_event_processor'; import { getMockSyncCache } from '../tests/mock/mock_cache'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ProcessableEvent } from './event_processor'; -import { formatEvents } from './event_builder/build_event_v1'; +import { buildLogEvent } from './event_builder/build_event_v1'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { advanceTimersByTime } from '../../tests/testUtils'; import { getMockLogger } from '../tests/mock/mock_logger'; @@ -139,9 +139,9 @@ describe('QueueingEventProcessor', async () => { await exhaustMicrotasks(); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([events[0], events[1]])); - expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents([events[2], events[3]])); - expect(mockDispatch.mock.calls[2][0]).toEqual(formatEvents([events[4]])); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([events[0], events[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent([events[2], events[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(buildLogEvent([events[4]])); }); }); @@ -202,7 +202,7 @@ describe('QueueingEventProcessor', async () => { await processor.process(event); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(formatEvents(events)); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); events = [event]; for(let i = 101; i < 200; i++) { @@ -217,7 +217,7 @@ describe('QueueingEventProcessor', async () => { await processor.process(event); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); - expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(formatEvents(events)); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(buildLogEvent(events)); }); it('should flush queue is context of the new event is different and enqueue the new event', async () => { @@ -250,11 +250,11 @@ describe('QueueingEventProcessor', async () => { await processor.process(newEvent); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(formatEvents(events)); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); await dispatchRepeater.execute(0); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); - expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(formatEvents([newEvent])); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(buildLogEvent([newEvent])); }); it('should store the event in the eventStore with increasing ids', async () => { @@ -313,7 +313,7 @@ describe('QueueingEventProcessor', async () => { await dispatchRepeater.execute(0); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(formatEvents(events)); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); events = []; for(let i = 1; i < 15; i++) { @@ -324,7 +324,7 @@ describe('QueueingEventProcessor', async () => { await dispatchRepeater.execute(0); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); - expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(formatEvents(events)); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(buildLogEvent(events)); }); it('should not retry failed dispatch if retryConfig is not provided', async () => { @@ -397,7 +397,7 @@ describe('QueueingEventProcessor', async () => { expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(4); expect(backoffController.backoff).toHaveBeenCalledTimes(3); - const request = formatEvents(events); + const request = buildLogEvent(events); for(let i = 0; i < 4; i++) { expect(eventDispatcher.dispatchEvent.mock.calls[i][0]).toEqual(request); } @@ -444,7 +444,7 @@ describe('QueueingEventProcessor', async () => { expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(201); expect(backoffController.backoff).toHaveBeenCalledTimes(200); - const request = formatEvents(events); + const request = buildLogEvent(events); for(let i = 0; i < 201; i++) { expect(eventDispatcher.dispatchEvent.mock.calls[i][0]).toEqual(request); } @@ -723,7 +723,7 @@ describe('QueueingEventProcessor', async () => { await exhaustMicrotasks(); expect(mockDispatch).toHaveBeenCalledTimes(1); - expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents(failedEvents)); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent(failedEvents)); const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); expect(eventsInStore).toEqual(expect.arrayContaining([ @@ -761,7 +761,7 @@ describe('QueueingEventProcessor', async () => { dispatchRepeater.execute(0); await exhaustMicrotasks(); expect(mockDispatch).toHaveBeenCalledTimes(1); - expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([eventA, eventB])); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([eventA, eventB])); const failedEvents: ProcessableEvent[] = []; @@ -776,7 +776,7 @@ describe('QueueingEventProcessor', async () => { await exhaustMicrotasks(); expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents(failedEvents)); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent(failedEvents)); mockResult2.resolve({}); await exhaustMicrotasks(); @@ -826,10 +826,10 @@ describe('QueueingEventProcessor', async () => { // events 0 1 4 5 6 7 have one context, and 2 3 have different context // batches should be [0, 1], [2, 3], [4, 5, 6], [7] expect(mockDispatch).toHaveBeenCalledTimes(4); - expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([failedEvents[0], failedEvents[1]])); - expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents([failedEvents[2], failedEvents[3]])); - expect(mockDispatch.mock.calls[2][0]).toEqual(formatEvents([failedEvents[4], failedEvents[5], failedEvents[6]])); - expect(mockDispatch.mock.calls[3][0]).toEqual(formatEvents([failedEvents[7]])); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([failedEvents[0], failedEvents[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent([failedEvents[2], failedEvents[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(buildLogEvent([failedEvents[4], failedEvents[5], failedEvents[6]])); + expect(mockDispatch.mock.calls[3][0]).toEqual(buildLogEvent([failedEvents[7]])); }); }); @@ -873,7 +873,7 @@ describe('QueueingEventProcessor', async () => { await exhaustMicrotasks(); expect(mockDispatch).toHaveBeenCalledTimes(1); - expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents(failedEvents)); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent(failedEvents)); const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); expect(eventsInStore).toEqual(expect.arrayContaining([ @@ -913,7 +913,7 @@ describe('QueueingEventProcessor', async () => { dispatchRepeater.execute(0); await exhaustMicrotasks(); expect(mockDispatch).toHaveBeenCalledTimes(1); - expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([eventA, eventB])); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([eventA, eventB])); const failedEvents: ProcessableEvent[] = []; @@ -928,7 +928,7 @@ describe('QueueingEventProcessor', async () => { await exhaustMicrotasks(); expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents(failedEvents)); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent(failedEvents)); mockResult2.resolve({}); await exhaustMicrotasks(); @@ -980,10 +980,10 @@ describe('QueueingEventProcessor', async () => { // events 0 1 4 5 6 7 have one context, and 2 3 have different context // batches should be [0, 1], [2, 3], [4, 5, 6], [7] expect(mockDispatch).toHaveBeenCalledTimes(4); - expect(mockDispatch.mock.calls[0][0]).toEqual(formatEvents([failedEvents[0], failedEvents[1]])); - expect(mockDispatch.mock.calls[1][0]).toEqual(formatEvents([failedEvents[2], failedEvents[3]])); - expect(mockDispatch.mock.calls[2][0]).toEqual(formatEvents([failedEvents[4], failedEvents[5], failedEvents[6]])); - expect(mockDispatch.mock.calls[3][0]).toEqual(formatEvents([failedEvents[7]])); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([failedEvents[0], failedEvents[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent([failedEvents[2], failedEvents[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(buildLogEvent([failedEvents[4], failedEvents[5], failedEvents[6]])); + expect(mockDispatch.mock.calls[3][0]).toEqual(buildLogEvent([failedEvents[7]])); }); }); @@ -1012,7 +1012,7 @@ describe('QueueingEventProcessor', async () => { await dispatchRepeater.execute(0); expect(dispatchListener).toHaveBeenCalledTimes(1); - expect(dispatchListener.mock.calls[0][0]).toEqual(formatEvents([event, event2])); + expect(dispatchListener.mock.calls[0][0]).toEqual(buildLogEvent([event, event2])); }); it('should remove event handler when function returned from onDispatch is called', async () => { @@ -1041,7 +1041,7 @@ describe('QueueingEventProcessor', async () => { await dispatchRepeater.execute(0); expect(dispatchListener).toHaveBeenCalledTimes(1); - expect(dispatchListener.mock.calls[0][0]).toEqual(formatEvents([event, event2])); + expect(dispatchListener.mock.calls[0][0]).toEqual(buildLogEvent([event, event2])); unsub(); @@ -1119,7 +1119,7 @@ describe('QueueingEventProcessor', async () => { processor.stop(); expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents(events)); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); }); it('should cancel retry of active dispatches', async () => { diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 3d000a5df..287510b46 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -16,8 +16,8 @@ import { EventProcessor, ProcessableEvent } from "./event_processor"; import { Cache } from "../utils/cache/cache"; -import { EventDispatcher, EventDispatcherResponse, EventV1Request } from "./event_dispatcher"; -import { formatEvents } from "./event_builder/build_event_v1"; +import { EventDispatcher, EventDispatcherResponse, LogEvent } from "./event_dispatcher"; +import { buildLogEvent } from "./event_builder/log_event"; import { BackoffController, ExponentialBackoff, IntervalRepeater, Repeater } from "../utils/repeater/repeater"; import { LoggerFacade } from "../modules/logging"; import { BaseService, ServiceState, StartupLog } from "../service"; @@ -26,7 +26,7 @@ import { RunResult, runWithRetry } from "../utils/executor/backoff_retry_runner" import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; import { EventEmitter } from "../utils/event_emitter/event_emitter"; import { IdGenerator } from "../utils/id_generator"; -import { areEventContextsEqual } from "./events"; +import { areEventContextsEqual } from "./event_builder/user_event"; export type EventWithId = { id: string; @@ -51,7 +51,7 @@ export type BatchEventProcessorConfig = { }; type EventBatch = { - request: EventV1Request, + request: LogEvent, ids: string[], } @@ -66,7 +66,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { private idGenerator: IdGenerator = new IdGenerator(); private runningTask: Map<string, RunResult<EventDispatcherResponse>> = new Map(); private dispatchingEventIds: Set<string> = new Set(); - private eventEmitter: EventEmitter<{ dispatch: EventV1Request }> = new EventEmitter(); + private eventEmitter: EventEmitter<{ dispatch: LogEvent }> = new EventEmitter(); private retryConfig?: RetryConfig; constructor(config: BatchEventProcessorConfig) { @@ -85,7 +85,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { this.failedEventRepeater?.setTask(() => this.retryFailedEvents()); } - onDispatch(handler: Consumer<EventV1Request>): Fn { + onDispatch(handler: Consumer<LogEvent>): Fn { return this.eventEmitter.on('dispatch', handler); } @@ -119,7 +119,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { if (currentBatch.length === this.batchSize || (currentBatch.length > 0 && !areEventContextsEqual(currentBatch[0].event, event.event))) { batches.push({ - request: formatEvents(currentBatch.map((e) => e.event)), + request: buildLogEvent(currentBatch.map((e) => e.event)), ids: currentBatch.map((e) => e.id), }); currentBatch = []; @@ -129,7 +129,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { if (currentBatch.length > 0) { batches.push({ - request: formatEvents(currentBatch.map((e) => e.event)), + request: buildLogEvent(currentBatch.map((e) => e.event)), ids: currentBatch.map((e) => e.id), }); } @@ -153,10 +153,10 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { }); this.eventQueue = []; - return { request: formatEvents(events), ids }; + return { request: buildLogEvent(events), ids }; } - private async executeDispatch(request: EventV1Request, closing = false): Promise<EventDispatcherResponse> { + private async executeDispatch(request: LogEvent, closing = false): Promise<EventDispatcherResponse> { const dispatcher = closing && this.closingEventDispatcher ? this.closingEventDispatcher : this.eventDispatcher; return dispatcher.dispatchEvent(request).then((res) => { if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { diff --git a/lib/event_processor/default_dispatcher.spec.ts b/lib/event_processor/default_dispatcher.spec.ts index f7cdc718f..e3f73ffad 100644 --- a/lib/event_processor/default_dispatcher.spec.ts +++ b/lib/event_processor/default_dispatcher.spec.ts @@ -15,9 +15,9 @@ */ import { expect, vi, describe, it } from 'vitest'; import { DefaultEventDispatcher } from './default_dispatcher'; -import { EventV1 } from './event_builder/build_event_v1'; +import { EventBatch } from './event_builder/build_event_v1'; -const getEvent = (): EventV1 => { +const getEvent = (): EventBatch => { return { account_id: 'string', project_id: 'string', diff --git a/lib/event_processor/default_dispatcher.ts b/lib/event_processor/default_dispatcher.ts index 3105b49e1..43cb062c3 100644 --- a/lib/event_processor/default_dispatcher.ts +++ b/lib/event_processor/default_dispatcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { RequestHandler } from '../utils/http_request_handler/http'; -import { EventDispatcher, EventDispatcherResponse, EventV1Request } from './event_dispatcher'; +import { EventDispatcher, EventDispatcherResponse, LogEvent } from './event_dispatcher'; export class DefaultEventDispatcher implements EventDispatcher { private requestHandler: RequestHandler; @@ -24,7 +24,7 @@ export class DefaultEventDispatcher implements EventDispatcher { } async dispatchEvent( - eventObj: EventV1Request + eventObj: LogEvent ): Promise<EventDispatcherResponse> { // Non-POST requests not supported if (eventObj.httpVerb !== 'POST') { diff --git a/lib/event_processor/event_builder/index.tests.js b/lib/event_processor/event_builder/index.tests.js deleted file mode 100644 index 4fd6053a9..000000000 --- a/lib/event_processor/event_builder/index.tests.js +++ /dev/null @@ -1,1708 +0,0 @@ -/** - * Copyright 2016-2021, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { assert } from 'chai'; - -import fns from '../../utils/fns'; -import testData from '../../tests/test_data'; -import projectConfig from '../../project_config/project_config'; -import packageJSON from '../../../package.json'; -import { getConversionEvent, getImpressionEvent } from './'; - -describe('lib/core/event_builder', function() { - describe('APIs', function() { - var mockLogger; - var configObj; - var clock; - - beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData.getTestProjectConfig()); - clock = sinon.useFakeTimers(new Date().getTime()); - sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - mockLogger = { - log: sinon.stub(), - }; - }); - - afterEach(function() { - clock.restore(); - fns.uuid.restore(); - }); - - describe('getImpressionEvent', function() { - it('should create proper params for getImpressionEvent without attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - enabled: true, - ruleType: 'experiment', - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with attributes as a string value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 'firefox', - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventOptions = { - attributes: { browser_type: 'firefox' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - variationId: '111128', - ruleKey: 'exp1', - flagKey: 'flagKey1', - enabled: false, - ruleType: 'experiment', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with attributes as a false value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: false, - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: false }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with attributes as a zero value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 0, - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: 0 }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should not fill in userFeatures for getImpressionEvent when attribute is not in the datafile', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { invalid_attribute: 'sorry_not_sorry' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - variationId: '111128', - userId: 'testUser', - logger: mockLogger, - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering enabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '595008', - experiment_id: '595010', - campaign_id: '595005', - metadata: { - flag_key: 'flagKey2', - rule_key: 'exp2', - rule_type: 'experiment', - variation_key: 'var', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '595005', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - experimentId: '595010', - ruleKey: 'exp2', - flagKey: 'flagKey2', - ruleType: 'experiment', - enabled: false, - variationId: '595008', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering disabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - v4ConfigObj.botFiltering = false; - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: false, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '595008', - experiment_id: '595010', - campaign_id: '595005', - metadata: { - flag_key: 'flagKey2', - rule_key: 'exp2', - rule_type: 'experiment', - variation_key: 'var', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '595005', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - experimentId: '595010', - ruleKey: 'exp2', - flagKey: 'flagKey2', - ruleType: 'experiment', - enabled: false, - variationId: '595008', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with typed attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - key: 'browser_type', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '323434545', - key: 'boolean_key', - type: 'custom', - value: true, - }, - { - entity_id: '616727838', - key: 'integer_key', - type: 'custom', - value: 10, - }, - { - entity_id: '808797686', - key: 'double_key', - type: 'custom', - value: 3.14, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { - browser_type: 'Chrome', - boolean_key: true, - integer_key: 10, - double_key: 3.14, - }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should remove invalid params from impression event payload', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - key: 'browser_type', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '808797687', - key: 'valid_positive_number', - type: 'custom', - value: Math.pow(2, 53), - }, - { - entity_id: '808797688', - key: 'valid_negative_number', - type: 'custom', - value: -Math.pow(2, 53), - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { - browser_type: 'Chrome', - valid_positive_number: Math.pow(2, 53), - valid_negative_number: -Math.pow(2, 53), - invalid_number: Math.pow(2, 53) + 2, - array: [1, 2, 3], - }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - - describe('getConversionEvent', function() { - it('should create proper params for getConversionEvent without attributes or event value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111095', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEvent', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getConversionEvent with attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 'firefox', - key: 'browser_type', - }, - ], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111095', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEvent', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: 'firefox' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getConversionEvent with event value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - revenue: 4200, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 4200, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 4200, - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getConversionEvent with attributes and event value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 'firefox', - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - revenue: 4200, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 4200, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: 'firefox' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 4200, - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should not fill in userFeatures for getConversion when attribute is not in the datafile', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { invalid_attribute: 'sorry_not_sorry' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - sinon.assert.calledOnce(mockLogger.log); - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering enabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '594089', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'item_bought', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - eventKey: 'item_bought', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering disabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - v4ConfigObj.botFiltering = false; - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: false, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '594089', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'item_bought', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - eventKey: 'item_bought', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create the correct snapshot for multiple experiments attached to the event', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111100', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEventWithMultipleExperiments', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEventWithMultipleExperiments', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should remove invalid params from conversion event payload', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [ - { - entity_id: '111094', - key: 'browser_type', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '808797687', - key: 'valid_positive_number', - type: 'custom', - value: Math.pow(2, 53), - }, - { - entity_id: '808797688', - key: 'valid_negative_number', - type: 'custom', - value: -Math.pow(2, 53), - }, - ], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111100', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEventWithMultipleExperiments', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEventWithMultipleExperiments', - logger: mockLogger, - userId: 'testUser', - attributes: { - browser_type: 'Chrome', - valid_positive_number: Math.pow(2, 53), - valid_negative_number: -Math.pow(2, 53), - invalid_number: -Math.pow(2, 53) - 2, - array: [1, 2, 3], - }, - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and event tags are passed it', function() { - it('should create proper params for getConversionEvent with event tags', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - }, - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and the event tags contain an entry for "revenue"', function() { - it('should include the revenue value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - revenue: 4200, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 4200, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 4200, - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should include revenue value of 0 in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - revenue: 0, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 0, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 0, - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and the revenue value is invalid', function() { - it('should not include the revenue value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - revenue: 'invalid revenue', - }, - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 'invalid revenue', - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - }); - - describe('and the event tags contain an entry for "value"', function() { - it('should include the event value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - value: '13.37', - }, - timestamp: Math.round(new Date().getTime()), - value: 13.37, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - value: '13.37', - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should include the falsy event values in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - value: '0.0', - }, - timestamp: Math.round(new Date().getTime()), - value: 0.0, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - value: '0.0', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and the event value is invalid', function() { - it('should not include the event value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - value: 'invalid value', - }, - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - value: 'invalid value', - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - }); - }); - - describe('createEventWithBucketingId', function() { - it('should send proper bucketingID with user attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [ - { - entity_id: '$opt_bucketing_id', - key: '$opt_bucketing_id', - type: 'custom', - value: 'variation', - }, - ], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111095', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEvent', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - attributes: { $opt_bucketing_id: 'variation' }, - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - }); - }); -}); diff --git a/lib/event_processor/event_builder/index.ts b/lib/event_processor/event_builder/index.ts deleted file mode 100644 index 813038f05..000000000 --- a/lib/event_processor/event_builder/index.ts +++ /dev/null @@ -1,322 +0,0 @@ -/** - * Copyright 2016-2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerFacade } from '../../modules/logging'; -import { EventV1 as CommonEventParams } from '../event_builder/build_event_v1'; - -import fns from '../../utils/fns'; -import { CONTROL_ATTRIBUTES, RESERVED_EVENT_KEYWORDS } from '../../utils/enums'; -import { - getAttributeId, - getEventId, - getLayerId, - getVariationKeyFromId, - ProjectConfig, -} from '../../project_config/project_config'; -import * as eventTagUtils from '../../utils/event_tag_utils'; -import { isAttributeValid } from '../../utils/attributes_validator'; -import { EventTags, UserAttributes, Event as EventLoggingEndpoint } from '../../shared_types'; - -const ACTIVATE_EVENT_KEY = 'campaign_activated'; -const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'; -const ENDPOINT = 'https://logx.optimizely.com/v1/events'; -const HTTP_VERB = 'POST'; - -interface ImpressionOptions { - // Object representing user attributes and values which need to be recorded - attributes?: UserAttributes; - // The client we are using: node or javascript - clientEngine: string; - // The version of the client - clientVersion: string; - // Object representing project configuration, including datafile information and mappings for quick lookup - configObj: ProjectConfig; - // Experiment for which impression needs to be recorded - experimentId: string | null; - // Key of an experiment for which impression needs to be recorded - ruleKey: string; - // Key for a feature flag - flagKey: string; - // Boolean representing if feature is enabled - enabled: boolean; - // Type for the decision source - ruleType: string; - // Event key representing the event which needs to be recorded - eventKey?: string; - // ID for variation which would be presented to user - variationId: string | null; - // Logger object - logger: LoggerFacade; - // ID for user - userId: string; -} - -interface ConversionEventOptions { - // Object representing user attributes and values which need to be recorded - attributes?: UserAttributes; - // The client we are using: node or javascript - clientEngine: string; - // The version of the client - clientVersion: string; - // Object representing project configuration, including datafile information and mappings for quick lookup - configObj: ProjectConfig; - // Event key representing the event which needs to be recorded - eventKey: string; - // Logger object - logger: LoggerFacade; - // ID for user - userId: string; - // Object with event-specific tags - eventTags?: EventTags; -} - -type Metadata = { - flag_key: string; - rule_key: string; - rule_type: string; - variation_key: string; - enabled: boolean; -} - -type Decision = { - campaign_id: string | null; - experiment_id: string | null; - variation_id: string | null; - metadata: Metadata; -} - -type SnapshotEvent = { - entity_id: string | null; - timestamp: number; - uuid: string; - key: string; - revenue?: number; - value?: number; - tags?: EventTags; -} - -interface Snapshot { - decisions?: Decision[]; - events: SnapshotEvent[]; -} - -/** - * Get params which are used same in both conversion and impression events - * @param {ImpressionOptions|ConversionEventOptions} options Object containing values needed to build impression/conversion event - * @return {CommonEventParams} Common params with properties that are used in both conversion and impression events - */ -function getCommonEventParams({ - attributes, - userId, - clientEngine, - clientVersion, - configObj, - logger, -}: ImpressionOptions | ConversionEventOptions): CommonEventParams { - - const anonymize_ip = configObj.anonymizeIP ? configObj.anonymizeIP : false; - const botFiltering = configObj.botFiltering; - - const visitor = { - snapshots: [], - visitor_id: userId, - attributes: [], - }; - - const commonParams: CommonEventParams = { - account_id: configObj.accountId, - project_id: configObj.projectId, - visitors: [visitor], - revision: configObj.revision, - client_name: clientEngine, - client_version: clientVersion, - anonymize_ip: anonymize_ip, - enrich_decisions: true, - }; - - if (attributes) { - // Omit attribute values that are not supported by the log endpoint. - Object.keys(attributes || {}).forEach(function(attributeKey) { - const attributeValue = attributes[attributeKey]; - if (isAttributeValid(attributeKey, attributeValue)) { - const attributeId = getAttributeId(configObj, attributeKey, logger); - if (attributeId) { - commonParams.visitors[0].attributes.push({ - entity_id: attributeId, - key: attributeKey, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: attributeValue!, - }); - } - } - }); - } - - - if (typeof botFiltering === 'boolean') { - commonParams.visitors[0].attributes.push({ - entity_id: CONTROL_ATTRIBUTES.BOT_FILTERING, - key: CONTROL_ATTRIBUTES.BOT_FILTERING, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: botFiltering, - }); - } - - return commonParams; -} - -/** - * Creates object of params specific to impression events - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string|null} experimentId ID of experiment for which impression needs to be recorded - * @param {string|null} variationId ID for variation which would be presented to user - * @param {string} ruleKey Key of experiment for which impression needs to be recorded - * @param {string} ruleType Type for the decision source - * @param {string} flagKey Key for a feature flag - * @param {boolean} enabled Boolean representing if feature is enabled - * @return {Snapshot} Impression event params - */ -function getImpressionEventParams( - configObj: ProjectConfig, - experimentId: string | null, - variationId: string | null, - ruleKey: string, - ruleType: string, - flagKey: string, - enabled: boolean -): Snapshot { - - const campaignId = experimentId ? getLayerId(configObj, experimentId) : null; - - let variationKey = variationId ? getVariationKeyFromId(configObj, variationId) : null; - variationKey = variationKey || ''; - - const impressionEventParams = { - decisions: [ - { - campaign_id: campaignId, - experiment_id: experimentId, - variation_id: variationId, - metadata: { - flag_key: flagKey, - rule_key: ruleKey, - rule_type: ruleType, - variation_key: variationKey, - enabled: enabled, - } - }, - ], - events: [ - { - entity_id: campaignId, - timestamp: fns.currentTimestamp(), - key: ACTIVATE_EVENT_KEY, - uuid: fns.uuid(), - }, - ], - }; - - return impressionEventParams; -} - -/** - * Creates object of params specific to conversion events - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} eventKey Event key representing the event which needs to be recorded - * @param {LoggerFacade} logger Logger object - * @param {EventTags} eventTags Values associated with the event. - * @return {Snapshot} Conversion event params - */ -function getVisitorSnapshot( - configObj: ProjectConfig, - eventKey: string, - logger: LoggerFacade, - eventTags?: EventTags, -): Snapshot { - const snapshot: Snapshot = { - events: [], - }; - - const eventDict: SnapshotEvent = { - entity_id: getEventId(configObj, eventKey), - timestamp: fns.currentTimestamp(), - uuid: fns.uuid(), - key: eventKey, - }; - - if (eventTags) { - const revenue = eventTagUtils.getRevenueValue(eventTags, logger); - if (revenue !== null) { - eventDict[RESERVED_EVENT_KEYWORDS.REVENUE] = revenue; - } - - const eventValue = eventTagUtils.getEventValue(eventTags, logger); - if (eventValue !== null) { - eventDict[RESERVED_EVENT_KEYWORDS.VALUE] = eventValue; - } - - eventDict['tags'] = eventTags; - } - snapshot.events.push(eventDict); - - return snapshot; -} - -/** - * Create impression event params to be sent to the logging endpoint - * @param {ImpressionOptions} options Object containing values needed to build impression event - * @return {EventLoggingEndpoint} Params to be used in impression event logging endpoint call - */ -export function getImpressionEvent(options: ImpressionOptions): EventLoggingEndpoint { - const commonParams = getCommonEventParams(options); - const impressionEventParams = getImpressionEventParams( - options.configObj, - options.experimentId, - options.variationId, - options.ruleKey, - options.ruleType, - options.flagKey, - options.enabled, - ); - commonParams.visitors[0].snapshots.push(impressionEventParams); - - const impressionEvent: EventLoggingEndpoint = { - httpVerb: HTTP_VERB, - url: ENDPOINT, - params: commonParams, - } - - return impressionEvent; -} - -/** - * Create conversion event params to be sent to the logging endpoint - * @param {ConversionEventOptions} options Object containing values needed to build conversion event - * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call - */ -export function getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint { - - const commonParams = getCommonEventParams(options); - const snapshot = getVisitorSnapshot(options.configObj, options.eventKey, options.logger, options.eventTags); - commonParams.visitors[0].snapshots = [snapshot]; - - const conversionEvent: EventLoggingEndpoint = { - httpVerb: HTTP_VERB, - url: ENDPOINT, - params: commonParams, - } - - return conversionEvent; -} diff --git a/lib/event_processor/event_builder/build_event_v1.spec.ts b/lib/event_processor/event_builder/log_event.spec.ts similarity index 98% rename from lib/event_processor/event_builder/build_event_v1.spec.ts rename to lib/event_processor/event_builder/log_event.spec.ts index b1082dc7e..54a9c2acf 100644 --- a/lib/event_processor/event_builder/build_event_v1.spec.ts +++ b/lib/event_processor/event_builder/log_event.spec.ts @@ -18,10 +18,10 @@ import { describe, it, expect } from 'vitest'; import { buildConversionEventV1, buildImpressionEventV1, - makeBatchedEventV1, -} from './build_event_v1'; + makeEventBatch, +} from './log_event'; -import { ImpressionEvent, ConversionEvent } from '../events' +import { ImpressionEvent, ConversionEvent } from './user_event'; describe('buildImpressionEventV1', () => { it('should build an ImpressionEventV1 when experiment and variation are defined', () => { @@ -637,7 +637,7 @@ describe('buildConversionEventV1', () => { }) }) -describe('makeBatchedEventV1', () => { +describe('makeEventBatch', () => { it('should batch Conversion and Impression events together', () => { const conversionEvent: ConversionEvent = { type: 'conversion', @@ -714,7 +714,7 @@ describe('makeBatchedEventV1', () => { enabled: true, } - const result = makeBatchedEventV1([impressionEvent, conversionEvent]) + const result = makeEventBatch([impressionEvent, conversionEvent]) expect(result).toEqual({ client_name: 'node-sdk', diff --git a/lib/event_processor/event_builder/build_event_v1.ts b/lib/event_processor/event_builder/log_event.ts similarity index 93% rename from lib/event_processor/event_builder/build_event_v1.ts rename to lib/event_processor/event_builder/log_event.ts index 2cd794ca0..d648690da 100644 --- a/lib/event_processor/event_builder/build_event_v1.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -17,17 +17,16 @@ import { EventTags, ConversionEvent, ImpressionEvent, -} from '../events'; + UserEvent, +} from './user_event'; -import { Event } from '../../shared_types'; - -type ProcessableEvent = ConversionEvent | ImpressionEvent +import { LogEvent } from '../event_dispatcher'; const ACTIVATE_EVENT_KEY = 'campaign_activated' const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' const BOT_FILTERING_KEY = '$opt_bot_filtering' -export type EventV1 = { +export type EventBatch = { account_id: string project_id: string revision: string @@ -89,10 +88,10 @@ export type SnapshotEvent = { * Given an array of batchable Decision or ConversionEvent events it returns * a single EventV1 with proper batching * - * @param {ProcessableEvent[]} events - * @returns {EventV1} + * @param {UserEvent[]} events + * @returns {EventBatch} */ -export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 { +export function makeEventBatch(events: UserEvent[]): EventBatch { const visitors: Visitor[] = [] const data = events[0] @@ -222,7 +221,7 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { * @export * @interface EventBuilderV1 */ -export function buildImpressionEventV1(data: ImpressionEvent): EventV1 { +export function buildImpressionEventV1(data: ImpressionEvent): EventBatch { const visitor = makeVisitor(data) visitor.snapshots.push(makeDecisionSnapshot(data)) @@ -240,7 +239,7 @@ export function buildImpressionEventV1(data: ImpressionEvent): EventV1 { } } -export function buildConversionEventV1(data: ConversionEvent): EventV1 { +export function buildConversionEventV1(data: ConversionEvent): EventBatch { const visitor = makeVisitor(data) visitor.snapshots.push(makeConversionSnapshot(data)) @@ -258,10 +257,10 @@ export function buildConversionEventV1(data: ConversionEvent): EventV1 { } } -export function formatEvents(events: ProcessableEvent[]): Event { +export function buildLogEvent(events: UserEvent[]): LogEvent { return { url: 'https://logx.optimizely.com/v1/events', httpVerb: 'POST', - params: makeBatchedEventV1(events), + params: makeEventBatch(events), } } diff --git a/lib/event_processor/event_builder/event_helpers.tests.js b/lib/event_processor/event_builder/user_event.tests.js similarity index 98% rename from lib/event_processor/event_builder/event_helpers.tests.js rename to lib/event_processor/event_builder/user_event.tests.js index b241ecaf0..085435f09 100644 --- a/lib/event_processor/event_builder/event_helpers.tests.js +++ b/lib/event_processor/event_builder/user_event.tests.js @@ -19,9 +19,9 @@ import { assert } from 'chai'; import fns from '../../utils/fns'; import * as projectConfig from '../../project_config/project_config'; import * as decision from '../../core/decision'; -import { buildImpressionEvent, buildConversionEvent } from './event_helpers'; +import { buildImpressionEvent, buildConversionEvent } from './user_event'; -describe('lib/event_builder/event_helpers', function() { +describe('user_event', function() { var configObj; beforeEach(function() { diff --git a/lib/event_processor/event_builder/event_helpers.ts b/lib/event_processor/event_builder/user_event.ts similarity index 78% rename from lib/event_processor/event_builder/event_helpers.ts rename to lib/event_processor/event_builder/user_event.ts index 58b5cdb08..4db0aa8a4 100644 --- a/lib/event_processor/event_builder/event_helpers.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2022, 2024, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../../modules/logging'; - -import fns from '../../utils/fns'; -import * as eventTagUtils from '../../utils/event_tag_utils'; -import * as attributesValidator from '../../utils/attributes_validator'; -import * as decision from '../../core/decision'; - -import { EventTags, UserAttributes } from '../../shared_types'; import { DecisionObj } from '../../core/decision_service'; +import * as decision from '../../core/decision'; +import { isAttributeValid } from '../../utils/attributes_validator'; +import * as eventTagUtils from '../../utils/event_tag_utils'; +import fns from '../../utils/fns'; import { getAttributeId, getEventId, @@ -29,90 +25,105 @@ import { ProjectConfig, } from '../../project_config/project_config'; +import { getLogger } from '../../modules/logging'; +import { UserAttributes } from '../../shared_types'; + const logger = getLogger('EVENT_BUILDER'); -interface ImpressionConfig { - decisionObj: DecisionObj; - userId: string; - flagKey: string; - enabled: boolean; - userAttributes?: UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; +export type VisitorAttribute = { + entityId: string + key: string + value: string | number | boolean } -type VisitorAttribute = { - entityId: string; - key: string; - value: string | number | boolean; +type EventContext = { + accountId: string; + projectId: string; + revision: string; + clientName: string; + clientVersion: string; + anonymizeIP: boolean; + botFiltering?: boolean; } -interface ImpressionEvent { - type: 'impression'; +export type BaseUserEvent = { + type: 'impression' | 'conversion'; timestamp: number; uuid: string; + context: EventContext; user: { id: string; attributes: VisitorAttribute[]; }; - context: EventContext; +}; + +export type ImpressionEvent = BaseUserEvent & { + type: 'impression'; + layer: { id: string | null; - }; + } | null; + experiment: { id: string | null; key: string; } | null; + variation: { id: string | null; key: string; } | null; - ruleKey: string, - flagKey: string, - ruleType: string, - enabled: boolean, + ruleKey: string; + flagKey: string; + ruleType: string; + enabled: boolean; +}; + +export type EventTags = { + [key: string]: string | number | null; +}; + +export type ConversionEvent = BaseUserEvent & { + type: 'conversion'; + + event: { + id: string | null; + key: string; + } + + revenue: number | null; + value: number | null; + tags?: EventTags; } -type EventContext = { - accountId: string; - projectId: string; - revision: string; - clientName: string; - clientVersion: string; - anonymizeIP: boolean; - botFiltering: boolean | undefined; +export type UserEvent = ImpressionEvent | ConversionEvent; + +export const areEventContextsEqual = (eventA: UserEvent, eventB: UserEvent): boolean => { + const contextA = eventA.context + const contextB = eventB.context + return ( + contextA.accountId === contextB.accountId && + contextA.projectId === contextB.projectId && + contextA.clientName === contextB.clientName && + contextA.clientVersion === contextB.clientVersion && + contextA.revision === contextB.revision && + contextA.anonymizeIP === contextB.anonymizeIP && + contextA.botFiltering === contextB.botFiltering + ) } -interface ConversionConfig { - eventKey: string; - eventTags?: EventTags; +export type ImpressionConfig = { + decisionObj: DecisionObj; userId: string; + flagKey: string; + enabled: boolean; userAttributes?: UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; } -interface ConversionEvent { - type: 'conversion'; - timestamp: number; - uuid: string; - user: { - id: string; - attributes: VisitorAttribute[]; - }; - context: EventContext; - event: { - id: string | null; - key: string; - }; - revenue: number | null; - value: number | null; - tags: EventTags | undefined; -} - /** * Creates an ImpressionEvent object from decision data @@ -179,6 +190,16 @@ export const buildImpressionEvent = function({ }; }; +export type ConversionConfig = { + eventKey: string; + eventTags?: EventTags; + userId: string; + userAttributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; +} + /** * Creates a ConversionEvent object from track * @param {ConversionConfig} config @@ -230,16 +251,17 @@ export const buildConversionEvent = function({ }; }; -function buildVisitorAttributes( + +const buildVisitorAttributes = ( configObj: ProjectConfig, attributes?: UserAttributes -): VisitorAttribute[] { +): VisitorAttribute[] => { const builtAttributes: VisitorAttribute[] = []; // Omit attribute values that are not supported by the log endpoint. if (attributes) { Object.keys(attributes || {}).forEach(function(attributeKey) { const attributeValue = attributes[attributeKey]; - if (attributesValidator.isAttributeValid(attributeKey, attributeValue)) { + if (isAttributeValid(attributeKey, attributeValue)) { const attributeId = getAttributeId(configObj, attributeKey, logger); if (attributeId) { builtAttributes.push({ diff --git a/lib/event_processor/event_dispatcher.ts b/lib/event_processor/event_dispatcher.ts index 3872e6e90..f58c3fea2 100644 --- a/lib/event_processor/event_dispatcher.ts +++ b/lib/event_processor/event_dispatcher.ts @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventV1 } from "./event_builder/build_event_v1"; +import { EventBatch } from "./event_builder/log_event"; export type EventDispatcherResponse = { statusCode?: number } export interface EventDispatcher { - dispatchEvent(event: EventV1Request): Promise<EventDispatcherResponse> + dispatchEvent(event: LogEvent): Promise<EventDispatcherResponse> } -export interface EventV1Request { +export interface LogEvent { url: string httpVerb: 'POST' | 'PUT' | 'GET' | 'PATCH' - params: EventV1, + params: EventBatch, } diff --git a/lib/event_processor/event_processor.ts b/lib/event_processor/event_processor.ts index 1aee1a857..29df80a6c 100644 --- a/lib/event_processor/event_processor.ts +++ b/lib/event_processor/event_processor.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ConversionEvent, ImpressionEvent } from './events' -import { EventV1Request } from './event_dispatcher' +import { ConversionEvent, ImpressionEvent } from './event_builder/user_event' +import { LogEvent } from './event_dispatcher' import { getLogger } from '../modules/logging' import { Service } from '../service' import { Consumer, Fn } from '../utils/type'; @@ -26,5 +26,5 @@ export type ProcessableEvent = ConversionEvent | ImpressionEvent export interface EventProcessor extends Service { process(event: ProcessableEvent): Promise<unknown>; - onDispatch(handler: Consumer<EventV1Request>): Fn; + onDispatch(handler: Consumer<LogEvent>): Fn; } diff --git a/lib/event_processor/events.ts b/lib/event_processor/events.ts deleted file mode 100644 index 4254a274f..000000000 --- a/lib/event_processor/events.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export type VisitorAttribute = { - entityId: string - key: string - value: string | number | boolean -} - -export interface BaseEvent { - type: 'impression' | 'conversion' - timestamp: number - uuid: string - - // projectConfig stuff - context: { - accountId: string - projectId: string - clientName: string - clientVersion: string - revision: string - anonymizeIP: boolean - botFiltering?: boolean - } -} - -export interface ImpressionEvent extends BaseEvent { - type: 'impression' - - user: { - id: string - attributes: VisitorAttribute[] - } - - layer: { - id: string | null - } | null - - experiment: { - id: string | null - key: string - } | null - - variation: { - id: string | null - key: string - } | null - - ruleKey: string - flagKey: string - ruleType: string - enabled: boolean -} - -export interface ConversionEvent extends BaseEvent { - type: 'conversion' - - user: { - id: string - attributes: VisitorAttribute[] - } - - event: { - id: string | null - key: string - } - - revenue: number | null - value: number | null - tags: EventTags | undefined -} - -export type EventTags = { - [key: string]: string | number | null -} - -export function areEventContextsEqual(eventA: BaseEvent, eventB: BaseEvent): boolean { - const contextA = eventA.context - const contextB = eventB.context - return ( - contextA.accountId === contextB.accountId && - contextA.projectId === contextB.projectId && - contextA.clientName === contextB.clientName && - contextA.clientVersion === contextB.clientVersion && - contextA.revision === contextB.revision && - contextA.anonymizeIP === contextB.anonymizeIP && - contextA.botFiltering === contextB.botFiltering - ) -} diff --git a/lib/event_processor/forwarding_event_processor.spec.ts b/lib/event_processor/forwarding_event_processor.spec.ts index 3675c010f..3651df273 100644 --- a/lib/event_processor/forwarding_event_processor.spec.ts +++ b/lib/event_processor/forwarding_event_processor.spec.ts @@ -17,7 +17,7 @@ import { expect, describe, it, vi } from 'vitest'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher'; -import { formatEvents, makeBatchedEventV1 } from './event_builder/build_event_v1'; +import { buildLogEvent, makeEventBatch } from './event_builder/build_event_v1'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ServiceState } from '../service'; @@ -50,7 +50,7 @@ describe('ForwardingEventProcessor', () => { processor.process(event); expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); const data = mockDispatch.mock.calls[0][0].params; - expect(data).toEqual(makeBatchedEventV1([event])); + expect(data).toEqual(makeEventBatch([event])); }); it('should emit dispatch event when event is dispatched', async() => { @@ -67,9 +67,9 @@ describe('ForwardingEventProcessor', () => { const event = createImpressionEvent(); processor.process(event); expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); - expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents([event])); + expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent([event])); expect(listener).toHaveBeenCalledOnce(); - expect(listener).toHaveBeenCalledWith(formatEvents([event])); + expect(listener).toHaveBeenCalledWith(buildLogEvent([event])); }); it('should remove dispatch listener when the function returned from onDispatch is called', async() => { @@ -86,9 +86,9 @@ describe('ForwardingEventProcessor', () => { let event = createImpressionEvent(); processor.process(event); expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); - expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(formatEvents([event])); + expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent([event])); expect(listener).toHaveBeenCalledOnce(); - expect(listener).toHaveBeenCalledWith(formatEvents([event])); + expect(listener).toHaveBeenCalledWith(buildLogEvent([event])); unsub(); event = createImpressionEvent('id-a'); diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 99bccabd2..caf0752aa 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -15,17 +15,17 @@ */ -import { EventV1Request } from './event_dispatcher'; +import { LogEvent } from './event_dispatcher'; import { EventProcessor, ProcessableEvent } from './event_processor'; import { EventDispatcher } from '../shared_types'; -import { formatEvents } from './event_builder/build_event_v1'; +import { buildLogEvent } from './event_builder/log_event'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; - private eventEmitter: EventEmitter<{ dispatch: EventV1Request }>; + private eventEmitter: EventEmitter<{ dispatch: LogEvent }>; constructor(dispatcher: EventDispatcher) { super(); @@ -34,7 +34,7 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { } process(event: ProcessableEvent): Promise<unknown> { - const formattedEvent = formatEvents([event]); + const formattedEvent = buildLogEvent([event]); const res = this.dispatcher.dispatchEvent(formattedEvent); this.eventEmitter.emit('dispatch', formattedEvent); return res; @@ -61,7 +61,7 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { this.stopPromise.resolve(); } - onDispatch(handler: Consumer<EventV1Request>): Fn { + onDispatch(handler: Consumer<LogEvent>): Fn { return this.eventEmitter.on('dispatch', handler); } } diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index a66840215..00e9d69cc 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -6106,7 +6106,6 @@ describe('lib/optimizely', function() { userContext: user, reasons: [], }; - console.log(decisionsMap); assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 8122c50e7..cb15e1ed3 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -41,8 +41,9 @@ import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; import { ProjectConfigManager } from '../project_config/project_config_manager'; import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; -import { getImpressionEvent, getConversionEvent } from '../event_processor/event_builder'; -import { buildImpressionEvent, buildConversionEvent } from '../event_processor/event_builder/event_helpers'; +// import { getImpressionEvent, getConversionEvent } from '../event_processor/event_builder'; +import { buildLogEvent } from '../event_processor/event_builder/log_event'; +import { buildImpressionEvent, buildConversionEvent, ImpressionEvent } from '../event_processor/event_builder/user_event'; import fns from '../utils/fns'; import { validate } from '../utils/attributes_validator'; import * as eventTagsValidator from '../utils/event_tags_validator'; @@ -302,68 +303,16 @@ export default class Optimizely implements Client { clientVersion: this.clientVersion, configObj: configObj, }); - // TODO is it okay to not pass a projectConfig as second argument - this.eventProcessor.process(impressionEvent); - this.emitNotificationCenterActivate(decisionObj, flagKey, userId, enabled, attributes); - } - - /** - * Emit the ACTIVATE notification on the notificationCenter - * @param {DecisionObj} decisionObj Decision object - * @param {string} flagKey Key for a feature flag - * @param {string} userId ID of user to whom the variation was shown - * @param {boolean} enabled Boolean representing if feature is enabled - * @param {UserAttributes} attributes Optional user attributes - */ - private emitNotificationCenterActivate( - decisionObj: DecisionObj, - flagKey: string, - userId: string, - enabled: boolean, - attributes?: UserAttributes - ): void { - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - const ruleType = decisionObj.decisionSource; - const experimentKey = decision.getExperimentKey(decisionObj); - const experimentId = decision.getExperimentId(decisionObj); - const variationKey = decision.getVariationKey(decisionObj); - const variationId = decision.getVariationId(decisionObj); - - let experiment; - if (experimentId !== null && variationKey !== '') { - experiment = configObj.experimentIdMap[experimentId]; - } + this.eventProcessor.process(impressionEvent); - const impressionEventOptions = { - attributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - experimentId: experimentId, - ruleKey: experimentKey, - flagKey: flagKey, - ruleType: ruleType, - userId: userId, - enabled: enabled, - variationId: variationId, - logger: this.logger, - }; - const impressionEvent = getImpressionEvent(impressionEventOptions); - let variation; - if (experiment && experiment.variationKeyMap && variationKey !== '') { - variation = experiment.variationKeyMap[variationKey]; - } + const logEvent = buildLogEvent([impressionEvent]); this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, { - experiment: experiment, + experiment: decisionObj.experiment, userId: userId, attributes: attributes, - variation: variation, - logEvent: impressionEvent, + variation: decisionObj.variation, + logEvent, }); } @@ -415,57 +364,22 @@ export default class Optimizely implements Client { this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId); // TODO is it okay to not pass a projectConfig as second argument this.eventProcessor.process(conversionEvent); - this.emitNotificationCenterTrack(eventKey, userId, attributes, eventTags); + + const logEvent = buildLogEvent([conversionEvent]); + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.TRACK, { + eventKey, + userId, + attributes, + eventTags, + logEvent, + }); } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); } } - /** - * Send TRACK event to notificationCenter - * @param {string} eventKey - * @param {string} userId - * @param {UserAttributes} attributes - * @param {EventTags} eventTags Values associated with the event. - */ - private emitNotificationCenterTrack( - eventKey: string, - userId: string, - attributes?: UserAttributes, - eventTags?: EventTags - ): void { - try { - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - const conversionEventOptions = { - attributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - eventKey: eventKey, - eventTags: eventTags, - logger: this.logger, - userId: userId, - }; - const conversionEvent = getConversionEvent(conversionEventOptions); - - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.TRACK, { - eventKey: eventKey, - userId: userId, - attributes: attributes, - eventTags: eventTags, - logEvent: conversionEvent, - }); - } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); - } - } - + /** * Gets variation where visitor will be bucketed. * @param {string} experimentKey diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index 1be540540..9836afa14 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventTags } from '../../event_processor/events'; +import { EventTags } from '../../event_processor/event_builder/user_event'; import { LoggerFacade } from '../../modules/logging'; import { diff --git a/lib/utils/fns/index.ts b/lib/utils/fns/index.ts index 056278548..98606a77a 100644 --- a/lib/utils/fns/index.ts +++ b/lib/utils/fns/index.ts @@ -41,7 +41,7 @@ export function assign(target: any, ...sources: any[]): any { } } -function currentTimestamp(): number { +export function currentTimestamp(): number { return Math.round(new Date().getTime()); } From 95ea7d36c1c743c28dee36e1097c39dfaefd4ab9 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 26 Nov 2024 19:51:37 +0600 Subject: [PATCH 098/200] [FSSDK-10941] event processor files and directories cleanup - part 3 (#969) --- lib/core/decision_service/index.tests.js | 2 +- .../batch_event_processor.react_native.spec.ts | 2 +- lib/event_processor/batch_event_processor.spec.ts | 2 +- lib/event_processor/batch_event_processor.ts | 2 +- lib/event_processor/event_builder/log_event.ts | 2 +- .../default_dispatcher.browser.spec.ts | 4 ++-- .../{ => event_dispatcher}/default_dispatcher.browser.ts | 2 +- .../{ => event_dispatcher}/default_dispatcher.node.spec.ts | 4 ++-- .../{ => event_dispatcher}/default_dispatcher.node.ts | 2 +- .../{ => event_dispatcher}/default_dispatcher.spec.ts | 2 +- .../{ => event_dispatcher}/default_dispatcher.ts | 2 +- .../{ => event_dispatcher}/event_dispatcher.ts | 2 +- .../send_beacon_dispatcher.browser.spec.ts | 0 .../send_beacon_dispatcher.browser.ts | 0 lib/event_processor/event_processor.ts | 2 +- lib/event_processor/event_processor_factory.browser.spec.ts | 6 +++--- lib/event_processor/event_processor_factory.browser.ts | 6 +++--- lib/event_processor/event_processor_factory.node.spec.ts | 2 +- lib/event_processor/event_processor_factory.node.ts | 4 ++-- .../event_processor_factory.react_native.spec.ts | 2 +- lib/event_processor/event_processor_factory.react_native.ts | 4 ++-- lib/event_processor/event_processor_factory.ts | 2 +- lib/event_processor/forwarding_event_processor.spec.ts | 4 ++-- lib/event_processor/forwarding_event_processor.ts | 2 +- lib/index.browser.ts | 4 ++-- lib/index.node.ts | 2 +- lib/index.react_native.ts | 2 +- lib/shared_types.ts | 4 ++-- 28 files changed, 37 insertions(+), 37 deletions(-) rename lib/event_processor/{ => event_dispatcher}/default_dispatcher.browser.spec.ts (90%) rename lib/event_processor/{ => event_dispatcher}/default_dispatcher.browser.ts (89%) rename lib/event_processor/{ => event_dispatcher}/default_dispatcher.node.spec.ts (91%) rename lib/event_processor/{ => event_dispatcher}/default_dispatcher.node.ts (90%) rename lib/event_processor/{ => event_dispatcher}/default_dispatcher.spec.ts (98%) rename lib/event_processor/{ => event_dispatcher}/default_dispatcher.ts (95%) rename lib/event_processor/{ => event_dispatcher}/event_dispatcher.ts (93%) rename lib/event_processor/{ => event_dispatcher}/send_beacon_dispatcher.browser.spec.ts (100%) rename lib/event_processor/{ => event_dispatcher}/send_beacon_dispatcher.browser.ts (100%) diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 9ce0337e3..2ad87e07d 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -32,7 +32,7 @@ import OptimizelyUserContext from '../../optimizely_user_context'; import projectConfig, { createProjectConfig } from '../../project_config/project_config'; import AudienceEvaluator from '../audience_evaluator'; import errorHandler from '../../plugins/error_handler'; -import eventDispatcher from '../../event_processor/default_dispatcher.browser'; +import eventDispatcher from '../../event_processor/event_dispatcher/default_dispatcher.browser'; import * as jsonSchemaValidator from '../../utils/json_schema_validator'; import { getMockProjectConfigManager } from '../../tests/mock/mock_project_config_manager'; diff --git a/lib/event_processor/batch_event_processor.react_native.spec.ts b/lib/event_processor/batch_event_processor.react_native.spec.ts index b2b50fe0f..a30717d12 100644 --- a/lib/event_processor/batch_event_processor.react_native.spec.ts +++ b/lib/event_processor/batch_event_processor.react_native.spec.ts @@ -50,7 +50,7 @@ import { getMockRepeater } from '../tests/mock/mock_repeater'; import { getMockAsyncCache } from '../tests/mock/mock_cache'; import { EventWithId } from './batch_event_processor'; -import { buildLogEvent } from './event_builder/build_event_v1'; +import { buildLogEvent } from './event_builder/log_event'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ProcessableEvent } from './event_processor'; diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index 61318b92c..da02908ed 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -19,7 +19,7 @@ import { EventWithId, BatchEventProcessor } from './batch_event_processor'; import { getMockSyncCache } from '../tests/mock/mock_cache'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ProcessableEvent } from './event_processor'; -import { buildLogEvent } from './event_builder/build_event_v1'; +import { buildLogEvent } from './event_builder/log_event'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { advanceTimersByTime } from '../../tests/testUtils'; import { getMockLogger } from '../tests/mock/mock_logger'; diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 287510b46..f37708521 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -16,7 +16,7 @@ import { EventProcessor, ProcessableEvent } from "./event_processor"; import { Cache } from "../utils/cache/cache"; -import { EventDispatcher, EventDispatcherResponse, LogEvent } from "./event_dispatcher"; +import { EventDispatcher, EventDispatcherResponse, LogEvent } from "./event_dispatcher/event_dispatcher"; import { buildLogEvent } from "./event_builder/log_event"; import { BackoffController, ExponentialBackoff, IntervalRepeater, Repeater } from "../utils/repeater/repeater"; import { LoggerFacade } from "../modules/logging"; diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index d648690da..520ab4d0b 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -20,7 +20,7 @@ import { UserEvent, } from './user_event'; -import { LogEvent } from '../event_dispatcher'; +import { LogEvent } from '../event_dispatcher/event_dispatcher'; const ACTIVATE_EVENT_KEY = 'campaign_activated' const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' diff --git a/lib/event_processor/default_dispatcher.browser.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts similarity index 90% rename from lib/event_processor/default_dispatcher.browser.spec.ts rename to lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts index 4c35e39a7..bf83ca13b 100644 --- a/lib/event_processor/default_dispatcher.browser.spec.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts @@ -21,13 +21,13 @@ vi.mock('./default_dispatcher', () => { return { DefaultEventDispatcher }; }); -vi.mock('../utils/http_request_handler/browser_request_handler', () => { +vi.mock('../../utils/http_request_handler/browser_request_handler', () => { const BrowserRequestHandler = vi.fn(); return { BrowserRequestHandler }; }); import { DefaultEventDispatcher } from './default_dispatcher'; -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../../utils/http_request_handler/browser_request_handler'; import eventDispatcher from './default_dispatcher.browser'; describe('eventDispatcher', () => { diff --git a/lib/event_processor/default_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts similarity index 89% rename from lib/event_processor/default_dispatcher.browser.ts rename to lib/event_processor/event_dispatcher/default_dispatcher.browser.ts index 1dd72ab00..893039d92 100644 --- a/lib/event_processor/default_dispatcher.browser.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; +import { BrowserRequestHandler } from "../../utils/http_request_handler/browser_request_handler"; import { EventDispatcher } from './event_dispatcher'; import { DefaultEventDispatcher } from './default_dispatcher'; diff --git a/lib/event_processor/default_dispatcher.node.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts similarity index 91% rename from lib/event_processor/default_dispatcher.node.spec.ts rename to lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts index ddfc0c763..abd319b09 100644 --- a/lib/event_processor/default_dispatcher.node.spec.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts @@ -20,13 +20,13 @@ vi.mock('./default_dispatcher', () => { return { DefaultEventDispatcher }; }); -vi.mock('../utils/http_request_handler/node_request_handler', () => { +vi.mock('../../utils/http_request_handler/node_request_handler', () => { const NodeRequestHandler = vi.fn(); return { NodeRequestHandler }; }); import { DefaultEventDispatcher } from './default_dispatcher'; -import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../../utils/http_request_handler/node_request_handler'; import eventDispatcher from './default_dispatcher.node'; describe('eventDispatcher', () => { diff --git a/lib/event_processor/default_dispatcher.node.ts b/lib/event_processor/event_dispatcher/default_dispatcher.node.ts similarity index 90% rename from lib/event_processor/default_dispatcher.node.ts rename to lib/event_processor/event_dispatcher/default_dispatcher.node.ts index 130eaa6d2..52524140c 100644 --- a/lib/event_processor/default_dispatcher.node.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.node.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { EventDispatcher } from './event_dispatcher'; -import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../../utils/http_request_handler/node_request_handler'; import { DefaultEventDispatcher } from './default_dispatcher'; const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new NodeRequestHandler()); diff --git a/lib/event_processor/default_dispatcher.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts similarity index 98% rename from lib/event_processor/default_dispatcher.spec.ts rename to lib/event_processor/event_dispatcher/default_dispatcher.spec.ts index e3f73ffad..d491bf3a0 100644 --- a/lib/event_processor/default_dispatcher.spec.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts @@ -15,7 +15,7 @@ */ import { expect, vi, describe, it } from 'vitest'; import { DefaultEventDispatcher } from './default_dispatcher'; -import { EventBatch } from './event_builder/build_event_v1'; +import { EventBatch } from '../event_builder/log_event'; const getEvent = (): EventBatch => { return { diff --git a/lib/event_processor/default_dispatcher.ts b/lib/event_processor/event_dispatcher/default_dispatcher.ts similarity index 95% rename from lib/event_processor/default_dispatcher.ts rename to lib/event_processor/event_dispatcher/default_dispatcher.ts index 43cb062c3..b8c73833c 100644 --- a/lib/event_processor/default_dispatcher.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { RequestHandler } from '../utils/http_request_handler/http'; +import { RequestHandler } from '../../utils/http_request_handler/http'; import { EventDispatcher, EventDispatcherResponse, LogEvent } from './event_dispatcher'; export class DefaultEventDispatcher implements EventDispatcher { diff --git a/lib/event_processor/event_dispatcher.ts b/lib/event_processor/event_dispatcher/event_dispatcher.ts similarity index 93% rename from lib/event_processor/event_dispatcher.ts rename to lib/event_processor/event_dispatcher/event_dispatcher.ts index f58c3fea2..4dfda8f30 100644 --- a/lib/event_processor/event_dispatcher.ts +++ b/lib/event_processor/event_dispatcher/event_dispatcher.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventBatch } from "./event_builder/log_event"; +import { EventBatch } from "../event_builder/log_event"; export type EventDispatcherResponse = { statusCode?: number diff --git a/lib/event_processor/send_beacon_dispatcher.browser.spec.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.spec.ts similarity index 100% rename from lib/event_processor/send_beacon_dispatcher.browser.spec.ts rename to lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.spec.ts diff --git a/lib/event_processor/send_beacon_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts similarity index 100% rename from lib/event_processor/send_beacon_dispatcher.browser.ts rename to lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts diff --git a/lib/event_processor/event_processor.ts b/lib/event_processor/event_processor.ts index 29df80a6c..2bc4d5be0 100644 --- a/lib/event_processor/event_processor.ts +++ b/lib/event_processor/event_processor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ConversionEvent, ImpressionEvent } from './event_builder/user_event' -import { LogEvent } from './event_dispatcher' +import { LogEvent } from './event_dispatcher/event_dispatcher' import { getLogger } from '../modules/logging' import { Service } from '../service' import { Consumer, Fn } from '../utils/type'; diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts index e35dd1908..b0b636efb 100644 --- a/lib/event_processor/event_processor_factory.browser.spec.ts +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -43,14 +43,14 @@ vi.mock('../utils/cache/cache', () => { }); -import defaultEventDispatcher from './default_dispatcher.browser'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { SyncPrefixCache } from '../utils/cache/cache'; import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.browser'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; -import sendBeaconEventDispatcher from './send_beacon_dispatcher.browser'; +import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; import { getForwardingEventProcessor } from './forwarding_event_processor'; -import browserDefaultEventDispatcher from './default_dispatcher.browser'; +import browserDefaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { getBatchEventProcessor } from './event_processor_factory'; describe('createForwardingEventProcessor', () => { diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index 9456d06b1..b6651ed70 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -15,12 +15,12 @@ */ import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './event_dispatcher'; +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor'; import { EventWithId } from './batch_event_processor'; import { getBatchEventProcessor, BatchEventProcessorOptions } from './event_processor_factory'; -import defaultEventDispatcher from './default_dispatcher.browser'; -import sendBeaconEventDispatcher from './send_beacon_dispatcher.browser'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; +import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { SyncPrefixCache } from '../utils/cache/cache'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts index a511e2e06..31001400f 100644 --- a/lib/event_processor/event_processor_factory.node.spec.ts +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -42,7 +42,7 @@ vi.mock('../utils/cache/cache', () => { import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor_factory.node'; import { getForwardingEventProcessor } from './forwarding_event_processor'; -import nodeDefaultEventDispatcher from './default_dispatcher.node'; +import nodeDefaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { getBatchEventProcessor } from './event_processor_factory'; import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index 1a21fbf60..6c57272bc 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -14,9 +14,9 @@ * limitations under the License. */ import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './event_dispatcher'; +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor'; -import defaultEventDispatcher from './default_dispatcher.node'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; import { BatchEventProcessorOptions, FAILED_EVENT_RETRY_INTERVAL, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; export const createForwardingEventProcessor = ( diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 93e7a05ad..1ef075cd4 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -67,7 +67,7 @@ async function mockRequireNetInfo() { import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; import { getForwardingEventProcessor } from './forwarding_event_processor'; -import defaultEventDispatcher from './default_dispatcher.browser'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { getBatchEventProcessor } from './event_processor_factory'; import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index a007501a5..ce300ac79 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -14,9 +14,9 @@ * limitations under the License. */ import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './event_dispatcher'; +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor'; -import defaultEventDispatcher from './default_dispatcher.browser'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { BatchEventProcessorOptions, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { AsyncPrefixCache } from '../utils/cache/cache'; diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index adba35c1d..8221e7dab 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -17,7 +17,7 @@ import { LogLevel } from "../common_exports"; import { StartupLog } from "../service"; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; -import { EventDispatcher } from "./event_dispatcher"; +import { EventDispatcher } from "./event_dispatcher/event_dispatcher"; import { EventProcessor } from "./event_processor"; import { BatchEventProcessor, EventWithId, RetryConfig } from "./batch_event_processor"; import { AsyncPrefixCache, Cache, SyncPrefixCache } from "../utils/cache/cache"; diff --git a/lib/event_processor/forwarding_event_processor.spec.ts b/lib/event_processor/forwarding_event_processor.spec.ts index 3651df273..76b69a185 100644 --- a/lib/event_processor/forwarding_event_processor.spec.ts +++ b/lib/event_processor/forwarding_event_processor.spec.ts @@ -16,8 +16,8 @@ import { expect, describe, it, vi } from 'vitest'; import { getForwardingEventProcessor } from './forwarding_event_processor'; -import { EventDispatcher } from './event_dispatcher'; -import { buildLogEvent, makeEventBatch } from './event_builder/build_event_v1'; +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; +import { buildLogEvent, makeEventBatch } from './event_builder/log_event'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ServiceState } from '../service'; diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index caf0752aa..768c10e87 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -15,7 +15,7 @@ */ -import { LogEvent } from './event_dispatcher'; +import { LogEvent } from './event_dispatcher/event_dispatcher'; import { EventProcessor, ProcessableEvent } from './event_processor'; import { EventDispatcher } from '../shared_types'; diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 5821d0aa0..521cc773f 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -18,8 +18,8 @@ import logHelper from './modules/logging/logger'; import { getLogger, setErrorHandler, getErrorHandler, LogLevel } from './modules/logging'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; -import defaultEventDispatcher from './event_processor/default_dispatcher.browser'; -import sendBeaconEventDispatcher from './event_processor/send_beacon_dispatcher.browser'; +import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; +import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import { createNotificationCenter } from './core/notification_center'; diff --git a/lib/index.node.ts b/lib/index.node.ts index ba4290d53..606f3aa55 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -20,7 +20,7 @@ import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; -import defaultEventDispatcher from './event_processor/default_dispatcher.node'; +import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; import { createNotificationCenter } from './core/notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { NodeOdpManager } from './plugins/odp_manager/index.node'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 41cf71369..eda80e4e8 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -20,7 +20,7 @@ import Optimizely from './optimizely'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import * as loggerPlugin from './plugins/logger/index.react_native'; -import defaultEventDispatcher from './event_processor/default_dispatcher.browser'; +import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import { createNotificationCenter } from './core/notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index eb99d4578..bf3d238df 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -38,10 +38,10 @@ import { IUserAgentParser } from './core/odp/user_agent_parser'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; import { ProjectConfig } from './project_config/project_config'; import { ProjectConfigManager } from './project_config/project_config_manager'; -import { EventDispatcher } from './event_processor/event_dispatcher'; +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor/event_processor'; -export { EventDispatcher } from './event_processor/event_dispatcher'; +export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; export { EventProcessor } from './event_processor/event_processor'; export interface BucketerParams { experimentId: string; From 59292108185facead23c16eb9e89228efbb1eb52 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 26 Nov 2024 21:14:24 +0600 Subject: [PATCH 099/200] [FSSDK-10941] move notification_center to the lib root directory (#970) --- lib/core/decision_service/index.tests.js | 2 +- lib/index.browser.ts | 2 +- lib/index.lite.ts | 2 +- lib/index.node.ts | 2 +- lib/index.react_native.ts | 2 +- lib/{core => }/notification_center/index.tests.js | 6 +++--- lib/{core => }/notification_center/index.ts | 8 ++++---- lib/optimizely/index.spec.ts | 2 +- lib/optimizely/index.tests.js | 2 +- lib/optimizely/index.ts | 2 +- lib/optimizely_user_context/index.tests.js | 2 +- lib/shared_types.ts | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) rename lib/{core => }/notification_center/index.tests.js (99%) rename lib/{core => }/notification_center/index.ts (97%) diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 2ad87e07d..046850db9 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -26,7 +26,7 @@ import { } from '../../utils/enums'; import { createLogger } from '../../plugins/logger'; import { getForwardingEventProcessor } from '../../event_processor/forwarding_event_processor'; -import { createNotificationCenter } from '../notification_center'; +import { createNotificationCenter } from '../../notification_center'; import Optimizely from '../../optimizely'; import OptimizelyUserContext from '../../optimizely_user_context'; import projectConfig, { createProjectConfig } from '../../project_config/project_config'; diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 521cc773f..2a889c339 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -22,7 +22,7 @@ import defaultEventDispatcher from './event_processor/event_dispatcher/default_d import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; -import { createNotificationCenter } from './core/notification_center'; +import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import Optimizely from './optimizely'; diff --git a/lib/index.lite.ts b/lib/index.lite.ts index 5aec89ecb..a7cc7cf22 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -26,7 +26,7 @@ import defaultErrorHandler from './plugins/error_handler'; import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import Optimizely from './optimizely'; -import { createNotificationCenter } from './core/notification_center'; +import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, ConfigLite } from './shared_types'; import * as commonExports from './common_exports'; diff --git a/lib/index.node.ts b/lib/index.node.ts index 606f3aa55..0904c0142 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -21,7 +21,7 @@ import * as loggerPlugin from './plugins/logger'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; -import { createNotificationCenter } from './core/notification_center'; +import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { NodeOdpManager } from './plugins/odp_manager/index.node'; import * as commonExports from './common_exports'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index eda80e4e8..2736e40a8 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -21,7 +21,7 @@ import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import * as loggerPlugin from './plugins/logger/index.react_native'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; -import { createNotificationCenter } from './core/notification_center'; +import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; import * as commonExports from './common_exports'; diff --git a/lib/core/notification_center/index.tests.js b/lib/notification_center/index.tests.js similarity index 99% rename from lib/core/notification_center/index.tests.js rename to lib/notification_center/index.tests.js index 79dc2fd5f..e1459af41 100644 --- a/lib/core/notification_center/index.tests.js +++ b/lib/notification_center/index.tests.js @@ -17,9 +17,9 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { createNotificationCenter } from './'; -import * as enums from '../../utils/enums'; -import { createLogger } from '../../plugins/logger'; -import errorHandler from '../../plugins/error_handler'; +import * as enums from '../utils/enums'; +import { createLogger } from '../plugins/logger'; +import errorHandler from '../plugins/error_handler'; var LOG_LEVEL = enums.LOG_LEVEL; diff --git a/lib/core/notification_center/index.ts b/lib/notification_center/index.ts similarity index 97% rename from lib/core/notification_center/index.ts rename to lib/notification_center/index.ts index a0a91dffe..ee2135104 100644 --- a/lib/core/notification_center/index.ts +++ b/lib/notification_center/index.ts @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LogHandler, ErrorHandler } from '../../modules/logging'; -import { objectValues } from '../../utils/fns'; -import { NotificationListener, ListenerPayload } from '../../shared_types'; +import { LogHandler, ErrorHandler } from '../modules/logging'; +import { objectValues } from '../utils/fns'; +import { NotificationListener, ListenerPayload } from '../shared_types'; import { LOG_LEVEL, LOG_MESSAGES, NOTIFICATION_TYPES, -} from '../../utils/enums'; +} from '../utils/enums'; const MODULE_NAME = 'NOTIFICATION_CENTER'; diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index a4b88017f..ee1525e2d 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -20,7 +20,7 @@ import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_m import * as logger from '../plugins/logger'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; import { LOG_LEVEL } from '../common_exports'; -import { createNotificationCenter } from '../core/notification_center'; +import { createNotificationCenter } from '../notification_center'; import testData from '../tests/test_data'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; import { LoggerFacade } from '../modules/logging'; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 00e9d69cc..e7fc378f7 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -34,7 +34,7 @@ import * as jsonSchemaValidator from '../utils/json_schema_validator'; import * as projectConfig from '../project_config/project_config'; import testData from '../tests/test_data'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; -import { createNotificationCenter } from '../core/notification_center'; +import { createNotificationCenter } from '../notification_center'; import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index cb15e1ed3..8833c92b2 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -16,7 +16,7 @@ import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; -import { NotificationCenter } from '../core/notification_center'; +import { NotificationCenter } from '../notification_center'; import { EventProcessor } from '../event_processor/event_processor'; import { IOdpManager } from '../core/odp/odp_manager'; diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 0e169fa7b..a895d928d 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -23,7 +23,7 @@ import { NOTIFICATION_TYPES } from '../utils/enums'; import OptimizelyUserContext from './'; import { createLogger } from '../plugins/logger'; -import { createNotificationCenter } from '../core/notification_center'; +import { createNotificationCenter } from '../notification_center'; import Optimizely from '../optimizely'; import errorHandler from '../plugins/error_handler'; import { CONTROL_ATTRIBUTES, LOG_LEVEL, LOG_MESSAGES } from '../utils/enums'; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index bf3d238df..e8ed60e8b 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -21,7 +21,7 @@ import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; -import { NotificationCenter as NotificationCenterImpl } from './core/notification_center'; +import { NotificationCenter as NotificationCenterImpl } from './notification_center'; import { NOTIFICATION_TYPES } from './utils/enums'; import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; From 16e638ab8f9cee3e030a55b89cc38431318547f8 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 27 Nov 2024 21:20:38 +0600 Subject: [PATCH 100/200] [FSSDK-10950] restructure odp directories (#971) --- lib/index.browser.tests.js | 10 +++---- lib/index.browser.ts | 6 ++-- lib/index.node.ts | 2 +- lib/index.react_native.ts | 2 +- .../event_api_manager.browser.ts} | 10 +++---- .../event_manager/event_api_manager.node.ts} | 9 +++--- .../event_manager/event_manager.browser.ts} | 6 ++-- .../event_manager/event_manager.node.ts} | 6 ++-- .../odp => odp/event_manager}/odp_event.ts | 0 .../event_manager}/odp_event_api_manager.ts | 3 +- .../event_manager}/odp_event_manager.ts | 6 ++-- lib/{core => }/odp/odp_config.ts | 2 +- .../odp_manager.browser.ts} | 29 +++++++++---------- .../index.node.ts => odp/odp_manager.node.ts} | 24 +++++++-------- lib/{core => }/odp/odp_manager.ts | 19 ++++++------ lib/{core => }/odp/odp_response_schema.ts | 0 lib/{core => }/odp/odp_types.ts | 0 lib/{core => }/odp/odp_utils.ts | 0 .../odp_segment_api_manager.ts | 4 +-- .../segment_manager}/odp_segment_manager.ts | 2 +- .../optimizely_segment_option.ts | 0 .../ua_parser/ua_parser.browser.ts} | 4 +-- .../odp => odp/ua_parser}/user_agent_info.ts | 0 .../ua_parser}/user_agent_parser.ts | 0 lib/optimizely/index.ts | 6 ++-- lib/optimizely_user_context/index.ts | 2 +- lib/project_config/project_config.ts | 2 +- lib/shared_types.ts | 14 ++++----- tests/odpEventApiManager.spec.ts | 6 ++-- tests/odpEventManager.spec.ts | 18 ++++++------ tests/odpManager.browser.spec.ts | 28 +++++++----------- tests/odpManager.spec.ts | 16 +++++----- tests/odpSegmentApiManager.spec.ts | 2 +- tests/odpSegmentManager.spec.ts | 8 ++--- 34 files changed, 117 insertions(+), 129 deletions(-) rename lib/{plugins/odp/event_api_manager/index.browser.ts => odp/event_manager/event_api_manager.browser.ts} (85%) rename lib/{plugins/odp/event_api_manager/index.node.ts => odp/event_manager/event_api_manager.node.ts} (80%) rename lib/{plugins/odp/event_manager/index.browser.ts => odp/event_manager/event_manager.browser.ts} (89%) rename lib/{plugins/odp/event_manager/index.node.ts => odp/event_manager/event_manager.node.ts} (89%) rename lib/{core/odp => odp/event_manager}/odp_event.ts (100%) rename lib/{core/odp => odp/event_manager}/odp_event_api_manager.ts (97%) rename lib/{core/odp => odp/event_manager}/odp_event_manager.ts (98%) rename lib/{core => }/odp/odp_config.ts (97%) rename lib/{plugins/odp_manager/index.browser.ts => odp/odp_manager.browser.ts} (83%) rename lib/{plugins/odp_manager/index.node.ts => odp/odp_manager.node.ts} (82%) rename lib/{core => }/odp/odp_manager.ts (93%) rename lib/{core => }/odp/odp_response_schema.ts (100%) rename lib/{core => }/odp/odp_types.ts (100%) rename lib/{core => }/odp/odp_utils.ts (100%) rename lib/{core/odp => odp/segment_manager}/odp_segment_api_manager.ts (98%) rename lib/{core/odp => odp/segment_manager}/odp_segment_manager.ts (99%) rename lib/{core/odp => odp/segment_manager}/optimizely_segment_option.ts (100%) rename lib/{plugins/odp/user_agent_parser/index.browser.ts => odp/ua_parser/ua_parser.browser.ts} (87%) rename lib/{core/odp => odp/ua_parser}/user_agent_info.ts (100%) rename lib/{core/odp => odp/ua_parser}/user_agent_parser.ts (100%) diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 3d38655ed..15145c7a6 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -26,11 +26,11 @@ import configValidator from './utils/config_validator'; import OptimizelyUserContext from './optimizely_user_context'; import { LOG_MESSAGES, ODP_EVENT_ACTION } from './utils/enums'; -import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; -import { OdpConfig } from './core/odp/odp_config'; -import { BrowserOdpEventManager } from './plugins/odp/event_manager/index.browser'; -import { BrowserOdpEventApiManager } from './plugins/odp/event_api_manager/index.browser'; -import { OdpEvent } from './core/odp/odp_event'; +import { BrowserOdpManager } from './odp/odp_manager.browser'; +import { OdpConfig } from './odp/odp_config'; +import { BrowserOdpEventManager } from './odp/event_manager/event_manager.browser'; +import { BrowserOdpEventApiManager } from './odp/event_manager/event_api_manager.browser'; +import { OdpEvent } from './odp/event_manager/odp_event'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 2a889c339..05cc88075 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -24,10 +24,10 @@ import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; -import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; +import { BrowserOdpManager } from './odp/odp_manager.browser'; import Optimizely from './optimizely'; -import { IUserAgentParser } from './core/odp/user_agent_parser'; -import { getUserAgentParser } from './plugins/odp/user_agent_parser/index.browser'; +import { IUserAgentParser } from './odp/ua_parser/user_agent_parser'; +import { getUserAgentParser } from './odp/ua_parser/ua_parser.browser'; import * as commonExports from './common_exports'; import { PollingConfigManagerConfig } from './project_config/config_manager_factory'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; diff --git a/lib/index.node.ts b/lib/index.node.ts index 0904c0142..a5a3b2968 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -23,7 +23,7 @@ import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { NodeOdpManager } from './plugins/odp_manager/index.node'; +import { NodeOdpManager } from './odp/odp_manager.node'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 2736e40a8..c0417d588 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -23,7 +23,7 @@ import * as loggerPlugin from './plugins/logger/index.react_native'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { BrowserOdpManager } from './plugins/odp_manager/index.browser'; +import { BrowserOdpManager } from './odp/odp_manager.browser'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.react_native'; diff --git a/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/odp/event_manager/event_api_manager.browser.ts similarity index 85% rename from lib/plugins/odp/event_api_manager/index.browser.ts rename to lib/odp/event_manager/event_api_manager.browser.ts index e8feb29ee..26ed98136 100644 --- a/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/lib/odp/event_manager/event_api_manager.browser.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { OdpEvent } from '../../../core/odp/odp_event'; -import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; -import { LogLevel } from '../../../modules/logging'; -import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; -import { HttpMethod } from '../../../utils/http_request_handler/http'; +import { OdpEvent } from './odp_event'; +import { OdpEventApiManager } from './odp_event_api_manager'; +import { LogLevel } from '../../modules/logging'; +import { OdpConfig } from '../odp_config'; +import { HttpMethod } from '../../utils/http_request_handler/http'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; diff --git a/lib/plugins/odp/event_api_manager/index.node.ts b/lib/odp/event_manager/event_api_manager.node.ts similarity index 80% rename from lib/plugins/odp/event_api_manager/index.node.ts rename to lib/odp/event_manager/event_api_manager.node.ts index eea898787..3bf1f2ad4 100644 --- a/lib/plugins/odp/event_api_manager/index.node.ts +++ b/lib/odp/event_manager/event_api_manager.node.ts @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { OdpConfig } from '../odp_config'; +import { OdpEvent } from './odp_event' +import { OdpEventApiManager } from './odp_event_api_manager'; +import { HttpMethod } from '../../utils/http_request_handler/http'; -import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; -import { OdpEvent } from '../../../core/odp/odp_event'; -import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; -import { LogLevel } from '../../../modules/logging'; -import { HttpMethod } from '../../../utils/http_request_handler/http'; export class NodeOdpEventApiManager extends OdpEventApiManager { protected shouldSendEvents(events: OdpEvent[]): boolean { return true; diff --git a/lib/plugins/odp/event_manager/index.browser.ts b/lib/odp/event_manager/event_manager.browser.ts similarity index 89% rename from lib/plugins/odp/event_manager/index.browser.ts rename to lib/odp/event_manager/event_manager.browser.ts index 37fda62a3..4151c9b68 100644 --- a/lib/plugins/odp/event_manager/index.browser.ts +++ b/lib/odp/event_manager/event_manager.browser.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { IOdpEventManager, OdpEventManager } from '../../../core/odp/odp_event_manager'; -import { LogLevel } from '../../../modules/logging'; -import { OdpEvent } from "../../../core/odp/odp_event"; +import { IOdpEventManager, OdpEventManager } from './odp_event_manager'; +import { LogLevel } from '../../modules/logging'; +import { OdpEvent } from './odp_event'; const DEFAULT_BROWSER_QUEUE_SIZE = 100; diff --git a/lib/plugins/odp/event_manager/index.node.ts b/lib/odp/event_manager/event_manager.node.ts similarity index 89% rename from lib/plugins/odp/event_manager/index.node.ts rename to lib/odp/event_manager/event_manager.node.ts index f2cb3277d..e057755a9 100644 --- a/lib/plugins/odp/event_manager/index.node.ts +++ b/lib/odp/event_manager/event_manager.node.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { OdpEvent } from '../../../core/odp/odp_event'; -import { IOdpEventManager, OdpEventManager } from '../../../core/odp/odp_event_manager'; -import { LogLevel } from '../../../modules/logging'; +import { OdpEvent } from './odp_event'; +import { IOdpEventManager, OdpEventManager } from './odp_event_manager'; +import { LogLevel } from '../../modules/logging'; const DEFAULT_BATCH_SIZE = 10; const DEFAULT_FLUSH_INTERVAL_MSECS = 1000; diff --git a/lib/core/odp/odp_event.ts b/lib/odp/event_manager/odp_event.ts similarity index 100% rename from lib/core/odp/odp_event.ts rename to lib/odp/event_manager/odp_event.ts diff --git a/lib/core/odp/odp_event_api_manager.ts b/lib/odp/event_manager/odp_event_api_manager.ts similarity index 97% rename from lib/core/odp/odp_event_api_manager.ts rename to lib/odp/event_manager/odp_event_api_manager.ts index 6b3362f8c..2a5249a28 100644 --- a/lib/core/odp/odp_event_api_manager.ts +++ b/lib/odp/event_manager/odp_event_api_manager.ts @@ -17,8 +17,7 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { OdpEvent } from './odp_event'; import { HttpMethod, RequestHandler } from '../../utils/http_request_handler/http'; -import { OdpConfig } from './odp_config'; -import { ERROR_MESSAGES } from '../../utils/enums'; +import { OdpConfig } from '../odp_config'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; diff --git a/lib/core/odp/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts similarity index 98% rename from lib/core/odp/odp_event_manager.ts rename to lib/odp/event_manager/odp_event_manager.ts index 2ffbbeaa3..2b4d69e57 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -20,10 +20,10 @@ import { uuid } from '../../utils/fns'; import { ERROR_MESSAGES, ODP_USER_KEY, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from '../../utils/enums'; import { OdpEvent } from './odp_event'; -import { OdpConfig } from './odp_config'; +import { OdpConfig } from '../odp_config'; import { IOdpEventApiManager } from './odp_event_api_manager'; -import { invalidOdpDataFound } from './odp_utils'; -import { IUserAgentParser } from './user_agent_parser'; +import { invalidOdpDataFound } from '../odp_utils'; +import { IUserAgentParser } from '../ua_parser/user_agent_parser'; import { scheduleMicrotask } from '../../utils/microtask'; const MAX_RETRIES = 3; diff --git a/lib/core/odp/odp_config.ts b/lib/odp/odp_config.ts similarity index 97% rename from lib/core/odp/odp_config.ts rename to lib/odp/odp_config.ts index 4e4f41855..5003e1238 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/odp/odp_config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { checkArrayEquality } from '../../utils/fns'; +import { checkArrayEquality } from '../utils/fns'; export class OdpConfig { /** diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/odp/odp_manager.browser.ts similarity index 83% rename from lib/plugins/odp_manager/index.browser.ts rename to lib/odp/odp_manager.browser.ts index 5001dc59f..7168b5822 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/odp/odp_manager.browser.ts @@ -22,25 +22,24 @@ import { REQUEST_TIMEOUT_ODP_SEGMENTS_MS, REQUEST_TIMEOUT_ODP_EVENTS_MS, LOG_MESSAGES, -} from '../../utils/enums'; -import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +} from '../utils/enums'; +import { getLogger, LogHandler, LogLevel } from '../modules/logging'; -import { BrowserRequestHandler } from './../../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; -import BrowserAsyncStorageCache from '../key_value_cache/browserAsyncStorageCache'; -import PersistentKeyValueCache from '../key_value_cache/persistentKeyValueCache'; -import { BrowserLRUCache } from '../../utils/lru_cache'; +import BrowserAsyncStorageCache from '../plugins/key_value_cache/browserAsyncStorageCache'; +import { BrowserLRUCache } from '../utils/lru_cache'; -import { VuidManager } from './../vuid_manager/index'; +import { VuidManager } from '../plugins/vuid_manager/index'; -import { OdpManager } from '../../core/odp/odp_manager'; -import { OdpEvent } from '../../core/odp/odp_event'; -import { IOdpEventManager, OdpOptions } from '../../shared_types'; -import { BrowserOdpEventApiManager } from '../odp/event_api_manager/index.browser'; -import { BrowserOdpEventManager } from '../odp/event_manager/index.browser'; -import { IOdpSegmentManager, OdpSegmentManager } from '../../core/odp/odp_segment_manager'; -import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; -import { OdpConfig, OdpIntegrationConfig } from '../../core/odp/odp_config'; +import { OdpManager } from './odp_manager'; +import { OdpEvent } from './event_manager/odp_event'; +import { IOdpEventManager, OdpOptions } from '../shared_types'; +import { BrowserOdpEventApiManager } from './event_manager/event_api_manager.browser'; +import { BrowserOdpEventManager } from './event_manager/event_manager.browser'; +import { IOdpSegmentManager, OdpSegmentManager } from './segment_manager/odp_segment_manager'; +import { OdpSegmentApiManager } from './segment_manager/odp_segment_api_manager'; +import { OdpConfig, OdpIntegrationConfig } from './odp_config'; interface BrowserOdpManagerConfig { clientEngine?: string, diff --git a/lib/plugins/odp_manager/index.node.ts b/lib/odp/odp_manager.node.ts similarity index 82% rename from lib/plugins/odp_manager/index.node.ts rename to lib/odp/odp_manager.node.ts index 9eebc71d1..648e27751 100644 --- a/lib/plugins/odp_manager/index.node.ts +++ b/lib/odp/odp_manager.node.ts @@ -14,25 +14,25 @@ * limitations under the License. */ -import { NodeRequestHandler } from '../../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; -import { ServerLRUCache } from './../../utils/lru_cache/server_lru_cache'; +import { ServerLRUCache } from '../utils/lru_cache/server_lru_cache'; -import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; +import { getLogger, LogHandler, LogLevel } from '../modules/logging'; import { NODE_CLIENT_ENGINE, CLIENT_VERSION, REQUEST_TIMEOUT_ODP_EVENTS_MS, REQUEST_TIMEOUT_ODP_SEGMENTS_MS, -} from '../../utils/enums'; - -import { OdpManager } from '../../core/odp/odp_manager'; -import { IOdpEventManager, OdpOptions } from '../../shared_types'; -import { NodeOdpEventApiManager } from '../odp/event_api_manager/index.node'; -import { NodeOdpEventManager } from '../odp/event_manager/index.node'; -import { IOdpSegmentManager, OdpSegmentManager } from '../../core/odp/odp_segment_manager'; -import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; -import { OdpConfig, OdpIntegrationConfig } from '../../core/odp/odp_config'; +} from '../utils/enums'; + +import { OdpManager } from './odp_manager'; +import { IOdpEventManager, OdpOptions } from '../shared_types'; +import { NodeOdpEventApiManager } from './event_manager/event_api_manager.node'; +import { NodeOdpEventManager } from './event_manager/event_manager.node'; +import { IOdpSegmentManager, OdpSegmentManager } from './segment_manager/odp_segment_manager'; +import { OdpSegmentApiManager } from './segment_manager/odp_segment_api_manager'; +import { OdpConfig, OdpIntegrationConfig } from './odp_config'; interface NodeOdpManagerConfig { clientEngine?: string, diff --git a/lib/core/odp/odp_manager.ts b/lib/odp/odp_manager.ts similarity index 93% rename from lib/core/odp/odp_manager.ts rename to lib/odp/odp_manager.ts index 54278358d..df2bbc394 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -14,19 +14,18 @@ * limitations under the License. */ -import { LOG_MESSAGES } from './../../utils/enums/index'; -import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; -import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; +import { LogHandler, LogLevel } from '../modules/logging'; +import { ERROR_MESSAGES, ODP_USER_KEY } from '../utils/enums'; -import { VuidManager } from '../../plugins/vuid_manager'; +import { VuidManager } from '../plugins/vuid_manager'; -import { OdpConfig, OdpIntegrationConfig, odpIntegrationsAreEqual } from './odp_config'; -import { IOdpEventManager } from './odp_event_manager'; -import { IOdpSegmentManager } from './odp_segment_manager'; -import { OptimizelySegmentOption } from './optimizely_segment_option'; +import { OdpIntegrationConfig, odpIntegrationsAreEqual } from './odp_config'; +import { IOdpEventManager } from './event_manager/odp_event_manager'; +import { IOdpSegmentManager } from './segment_manager/odp_segment_manager'; +import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; import { invalidOdpDataFound } from './odp_utils'; -import { OdpEvent } from './odp_event'; -import { resolvablePromise, ResolvablePromise } from '../../utils/promise/resolvablePromise'; +import { OdpEvent } from './event_manager/odp_event'; +import { resolvablePromise, ResolvablePromise } from '../utils/promise/resolvablePromise'; /** * Manager for handling internal all business logic related to diff --git a/lib/core/odp/odp_response_schema.ts b/lib/odp/odp_response_schema.ts similarity index 100% rename from lib/core/odp/odp_response_schema.ts rename to lib/odp/odp_response_schema.ts diff --git a/lib/core/odp/odp_types.ts b/lib/odp/odp_types.ts similarity index 100% rename from lib/core/odp/odp_types.ts rename to lib/odp/odp_types.ts diff --git a/lib/core/odp/odp_utils.ts b/lib/odp/odp_utils.ts similarity index 100% rename from lib/core/odp/odp_utils.ts rename to lib/odp/odp_utils.ts diff --git a/lib/core/odp/odp_segment_api_manager.ts b/lib/odp/segment_manager/odp_segment_api_manager.ts similarity index 98% rename from lib/core/odp/odp_segment_api_manager.ts rename to lib/odp/segment_manager/odp_segment_api_manager.ts index 5978b3c6a..afe20ae2a 100644 --- a/lib/core/odp/odp_segment_api_manager.ts +++ b/lib/odp/segment_manager/odp_segment_api_manager.ts @@ -16,10 +16,10 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { validate } from '../../utils/json_schema_validator'; -import { OdpResponseSchema } from './odp_response_schema'; +import { OdpResponseSchema } from '../odp_response_schema'; import { ODP_USER_KEY } from '../../utils/enums'; import { RequestHandler, Response as HttpResponse } from '../../utils/http_request_handler/http'; -import { Response as GraphQLResponse } from './odp_types'; +import { Response as GraphQLResponse } from '../odp_types'; /** * Expected value for a qualified/valid segment diff --git a/lib/core/odp/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts similarity index 99% rename from lib/core/odp/odp_segment_manager.ts rename to lib/odp/segment_manager/odp_segment_manager.ts index ac92f5e33..4aaa47dc3 100644 --- a/lib/core/odp/odp_segment_manager.ts +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -18,7 +18,7 @@ import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; import { ICache } from '../../utils/lru_cache'; import { IOdpSegmentApiManager } from './odp_segment_api_manager'; -import { OdpConfig } from './odp_config'; +import { OdpConfig } from '../odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; export interface IOdpSegmentManager { diff --git a/lib/core/odp/optimizely_segment_option.ts b/lib/odp/segment_manager/optimizely_segment_option.ts similarity index 100% rename from lib/core/odp/optimizely_segment_option.ts rename to lib/odp/segment_manager/optimizely_segment_option.ts diff --git a/lib/plugins/odp/user_agent_parser/index.browser.ts b/lib/odp/ua_parser/ua_parser.browser.ts similarity index 87% rename from lib/plugins/odp/user_agent_parser/index.browser.ts rename to lib/odp/ua_parser/ua_parser.browser.ts index ad5d5f230..e6cc27dc8 100644 --- a/lib/plugins/odp/user_agent_parser/index.browser.ts +++ b/lib/odp/ua_parser/ua_parser.browser.ts @@ -15,8 +15,8 @@ */ import { UAParser } from 'ua-parser-js'; -import { UserAgentInfo } from "../../../core/odp/user_agent_info"; -import { IUserAgentParser } from '../../../core/odp/user_agent_parser'; +import { UserAgentInfo } from './user_agent_info'; +import { IUserAgentParser } from './user_agent_parser'; const userAgentParser: IUserAgentParser = { parseUserAgentInfo(): UserAgentInfo { diff --git a/lib/core/odp/user_agent_info.ts b/lib/odp/ua_parser/user_agent_info.ts similarity index 100% rename from lib/core/odp/user_agent_info.ts rename to lib/odp/ua_parser/user_agent_info.ts diff --git a/lib/core/odp/user_agent_parser.ts b/lib/odp/ua_parser/user_agent_parser.ts similarity index 100% rename from lib/core/odp/user_agent_parser.ts rename to lib/odp/ua_parser/user_agent_parser.ts diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 8833c92b2..96ef632f3 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -19,9 +19,9 @@ import { sprintf, objectValues } from '../utils/fns'; import { NotificationCenter } from '../notification_center'; import { EventProcessor } from '../event_processor/event_processor'; -import { IOdpManager } from '../core/odp/odp_manager'; -import { OdpEvent } from '../core/odp/odp_event'; -import { OptimizelySegmentOption } from '../core/odp/optimizely_segment_option'; +import { IOdpManager } from '../odp/odp_manager'; +import { OdpEvent } from '../odp/event_manager/odp_event'; +import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; import { UserAttributes, diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index 92b307dbb..b2a524a5e 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -24,7 +24,7 @@ import { UserAttributes, } from '../shared_types'; import { CONTROL_ATTRIBUTES } from '../utils/enums'; -import { OptimizelySegmentOption } from '../core/odp/optimizely_segment_option'; +import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; interface OptimizelyUserContextConfig { optimizely: Optimizely; diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 314372a87..2f9de78ff 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -34,7 +34,7 @@ import { Integration, FeatureVariableValue, } from '../shared_types'; -import { OdpConfig, OdpIntegrationConfig } from '../core/odp/odp_config'; +import { OdpConfig, OdpIntegrationConfig } from '../odp/odp_config'; import { Transformer } from '../utils/type'; interface TryCreatingProjectConfigConfig { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index e8ed60e8b..f021124ab 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -28,13 +28,13 @@ import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_us import { ICache } from './utils/lru_cache'; import { RequestHandler } from './utils/http_request_handler/http'; -import { OptimizelySegmentOption } from './core/odp/optimizely_segment_option'; -import { IOdpSegmentApiManager } from './core/odp/odp_segment_api_manager'; -import { IOdpSegmentManager } from './core/odp/odp_segment_manager'; -import { IOdpEventApiManager } from './core/odp/odp_event_api_manager'; -import { IOdpEventManager } from './core/odp/odp_event_manager'; -import { IOdpManager } from './core/odp/odp_manager'; -import { IUserAgentParser } from './core/odp/user_agent_parser'; +import { OptimizelySegmentOption } from './odp/segment_manager/optimizely_segment_option'; +import { IOdpSegmentApiManager } from './odp/segment_manager/odp_segment_api_manager'; +import { IOdpSegmentManager } from './odp/segment_manager/odp_segment_manager'; +import { IOdpEventApiManager } from './odp/event_manager/odp_event_api_manager'; +import { IOdpEventManager } from './odp/event_manager/odp_event_manager'; +import { IOdpManager } from './odp/odp_manager'; +import { IUserAgentParser } from './odp/ua_parser/user_agent_parser'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; import { ProjectConfig } from './project_config/project_config'; import { ProjectConfigManager } from './project_config/project_config_manager'; diff --git a/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts index 518b1b07c..07632c72a 100644 --- a/tests/odpEventApiManager.spec.ts +++ b/tests/odpEventApiManager.spec.ts @@ -18,10 +18,10 @@ import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; import { anyString, anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { NodeOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; -import { OdpEvent } from '../lib/core/odp/odp_event'; +import { NodeOdpEventApiManager } from '../lib/odp/event_manager/event_api_manager.node'; +import { OdpEvent } from '../lib/odp/event_manager/odp_event'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; -import { OdpConfig } from '../lib/core/odp/odp_config'; +import { OdpConfig } from '../lib/odp/odp_config'; const data1 = new Map<string, unknown>(); data1.set('key11', 'value-1'); diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index ebd3b1838..38cf9d379 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -16,17 +16,17 @@ import { describe, beforeEach, afterEach, beforeAll, it, vi, expect } from 'vitest'; import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE, ERROR_MESSAGES } from '../lib/utils/enums'; -import { OdpConfig } from '../lib/core/odp/odp_config'; -import { Status } from '../lib/core/odp/odp_event_manager'; -import { BrowserOdpEventManager } from "../lib/plugins/odp/event_manager/index.browser"; -import { NodeOdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; -import { OdpEventManager } from '../lib/core/odp/odp_event_manager'; +import { OdpConfig } from '../lib/odp/odp_config'; +import { Status } from '../lib/odp/event_manager/odp_event_manager'; +import { BrowserOdpEventManager } from '../lib/odp/event_manager/event_manager.browser'; +import { NodeOdpEventManager } from '../lib/odp/event_manager/event_manager.node'; +import { OdpEventManager } from '../lib/odp/event_manager/odp_event_manager'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; -import { IOdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; +import { IOdpEventApiManager } from '../lib/odp/event_manager/odp_event_api_manager'; import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { OdpEvent } from '../lib/core/odp/odp_event'; -import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; -import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; +import { OdpEvent } from '../lib/odp/event_manager/odp_event'; +import { IUserAgentParser } from '../lib/odp/ua_parser/user_agent_parser'; +import { UserAgentInfo } from '../lib/odp/ua_parser/user_agent_info'; import { resolve } from 'path'; import { advanceTimersByTime } from './testUtils'; diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index 89ecc8030..ee9415a78 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -13,31 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { describe, beforeEach, beforeAll, it, vi, expect } from 'vitest'; +import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; -import { anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; - -import { LOG_MESSAGES, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from './../lib/utils/enums/index'; -import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; +import { instance, mock, resetCalls } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; -import { BrowserOdpManager } from './../lib/plugins/odp_manager/index.browser'; -import { IOdpEventManager, OdpOptions } from './../lib/shared_types'; -import { OdpConfig } from '../lib/core/odp/odp_config'; -import { BrowserOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.browser'; -import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; -import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; +import { BrowserOdpManager } from './../lib/odp/odp_manager.browser'; + +import { OdpConfig } from '../lib/odp/odp_config'; +import { BrowserOdpEventApiManager } from '../lib/odp/event_manager/event_api_manager.browser'; +import { OdpSegmentManager } from './../lib/odp/segment_manager/odp_segment_manager'; +import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; -import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; -import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; -import { OdpEvent } from '../lib/core/odp/odp_event'; -import { LRUCache } from '../lib/utils/lru_cache'; -import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.browser'; -import { OdpManager } from '../lib/core/odp/odp_manager'; +import { BrowserOdpEventManager } from '../lib/odp/event_manager/event_manager.browser'; +import { OdpOptions } from '../lib/shared_types'; + const keyA = 'key-a'; const hostA = 'host-a'; diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 009f9997b..96f69b353 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -21,18 +21,16 @@ import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; import { LogHandler, LogLevel } from '../lib/modules/logging'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; -import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; - -import { OdpManager, Status } from '../lib/core/odp/odp_manager'; -import { OdpConfig, OdpIntegratedConfig, OdpIntegrationConfig, OdpNotIntegratedConfig } from '../lib/core/odp/odp_config'; -import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; -import { NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; -import { IOdpSegmentManager, OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; -import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; + +import { OdpManager, Status } from '../lib/odp/odp_manager'; +import { OdpConfig, OdpIntegratedConfig, OdpIntegrationConfig, OdpNotIntegratedConfig } from '../lib/odp/odp_config'; +import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/odp/event_manager/event_api_manager.node'; +import { NodeOdpEventManager as OdpEventManager } from '../lib/odp/event_manager/event_manager.node'; +import { IOdpSegmentManager, OdpSegmentManager } from './../lib/odp/segment_manager/odp_segment_manager'; +import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; import { IOdpEventManager } from '../lib/shared_types'; import { wait } from './testUtils'; import { resolvablePromise } from '../lib/utils/promise/resolvablePromise'; -import exp from 'constants'; const keyA = 'key-a'; const hostA = 'host-a'; diff --git a/tests/odpSegmentApiManager.spec.ts b/tests/odpSegmentApiManager.spec.ts index bcc82f698..ee8ebc482 100644 --- a/tests/odpSegmentApiManager.spec.ts +++ b/tests/odpSegmentApiManager.spec.ts @@ -18,7 +18,7 @@ import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; +import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { ODP_USER_KEY } from '../lib/utils/enums'; diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts index 723b40cd7..f10dbc353 100644 --- a/tests/odpSegmentManager.spec.ts +++ b/tests/odpSegmentManager.spec.ts @@ -22,11 +22,11 @@ import { LogHandler } from '../lib/modules/logging'; import { ODP_USER_KEY } from '../lib/utils/enums'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; -import { OdpSegmentManager } from '../lib/core/odp/odp_segment_manager'; -import { OdpConfig } from '../lib/core/odp/odp_config'; +import { OdpSegmentManager } from '../lib/odp/segment_manager/odp_segment_manager'; +import { OdpConfig } from '../lib/odp/odp_config'; import { LRUCache } from '../lib/utils/lru_cache'; -import { OptimizelySegmentOption } from './../lib/core/odp/optimizely_segment_option'; -import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; +import { OptimizelySegmentOption } from './../lib/odp/segment_manager/optimizely_segment_option'; +import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; describe('OdpSegmentManager', () => { class MockOdpSegmentApiManager extends OdpSegmentApiManager { From bbe7a5b284947a913a8aa971421bf797b640ac77 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:28:33 +0600 Subject: [PATCH 101/200] Junaed/fssdk 10936 async storage dynamic import (#972) * [FSSDK-10936] async storage dynamic import --- .../async-storage-event-processor.ts | 1 + .../async-storage.ts | 71 +++++++-------- ...ent_processor_factory.react_native.spec.ts | 67 +++++++++++--- .../reactNativeAsyncStorageCache.ts | 12 +-- ...onfig_manager_factory.react_native.spec.ts | 89 +++++++++++++++++-- .../config_manager_factory.react_native.ts | 3 +- .../async_storage_cache.react_native.spec.ts | 59 +++++------- .../cache/async_storage_cache.react_native.ts | 15 ++-- .../async-storage.ts | 26 ++++++ tests/reactNativeAsyncStorageCache.spec.ts | 28 ++---- 10 files changed, 236 insertions(+), 135 deletions(-) create mode 100644 lib/utils/import.react_native/@react-native-async-storage/async-storage.ts diff --git a/__mocks__/@react-native-async-storage/async-storage-event-processor.ts b/__mocks__/@react-native-async-storage/async-storage-event-processor.ts index 1ba23231b..ad40f0152 100644 --- a/__mocks__/@react-native-async-storage/async-storage-event-processor.ts +++ b/__mocks__/@react-native-async-storage/async-storage-event-processor.ts @@ -36,6 +36,7 @@ export default class AsyncStorage { return new Promise(resolve => { setTimeout(() => { items[key] && delete items[key] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore resolve() }, 1) diff --git a/__mocks__/@react-native-async-storage/async-storage.ts b/__mocks__/@react-native-async-storage/async-storage.ts index 2cbc7fd9a..36d3cf85d 100644 --- a/__mocks__/@react-native-async-storage/async-storage.ts +++ b/__mocks__/@react-native-async-storage/async-storage.ts @@ -14,50 +14,43 @@ * limitations under the License. */ - let items: {[key: string]: string} = {} export default class AsyncStorage { - static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise((resolve, reject) => { - switch (key) { - case 'keyThatExists': - resolve('{ "name": "Awesome Object" }') - break - case 'keyThatDoesNotExist': - resolve(null) - break - case 'keyWithInvalidJsonObject': - resolve('bad json }') - break - default: - setTimeout(() => resolve(items[key] || null), 1) - } - }) - } + private static items: Record<string, string> = {}; - static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise<void> { - return new Promise((resolve) => { - setTimeout(() => { - items[key] = value - resolve() - }, 1) - }) + static getItem( + key: string, + callback?: (error?: Error, result?: string | null) => void + ): Promise<string | null> { + const value = AsyncStorage.items[key] || null; + callback?.(undefined, value); + return Promise.resolve(value); } - - static removeItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise(resolve => { - setTimeout(() => { - items[key] && delete items[key] - // @ts-ignore - resolve() - }, 1) - }) + + static setItem( + key: string, + value: string, + callback?: (error?: Error) => void + ): Promise<void> { + AsyncStorage.items[key] = value; + callback?.(undefined); + return Promise.resolve(); } - - static dumpItems(): {[key: string]: string} { - return items + + static removeItem( + key: string, + callback?: (error?: Error, result?: string | null) => void + ): Promise<string | null> { + const value = AsyncStorage.items[key] || null; + if (key in AsyncStorage.items) { + delete AsyncStorage.items[key]; + } + callback?.(undefined, value); + return Promise.resolve(value); } - static clearStore(): void { - items = {} + static clearStore(): Promise<void> { + AsyncStorage.items = {}; + return Promise.resolve(); } + } diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 1ef075cd4..18d066366 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -25,7 +25,7 @@ vi.mock('./forwarding_event_processor', () => { return { getForwardingEventProcessor }; }); -vi.mock('./event_processor_factory', async (importOriginal) => { +vi.mock('./event_processor_factory', async importOriginal => { const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); @@ -46,13 +46,14 @@ vi.mock('@react-native-community/netinfo', () => { }); let isNetInfoAvailable = false; +let isAsyncStorageAvailable = true; await vi.hoisted(async () => { await mockRequireNetInfo(); }); async function mockRequireNetInfo() { - const {Module} = await import('module'); + const { Module } = await import('module'); const M: any = Module; M._load_original = M._load; @@ -61,6 +62,11 @@ async function mockRequireNetInfo() { if (isNetInfoAvailable) return {}; throw new Error('Module not found: @react-native-community/netinfo'); } + if (uri === '@react-native-async-storage/async-storage') { + if (isAsyncStorageAvailable) return {}; + throw new Error('Module not found: @react-native-async-storage/async-storage'); + } + return M._load_original(uri, parent); }; } @@ -68,7 +74,7 @@ async function mockRequireNetInfo() { import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; -import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL, getPrefixEventStore } from './event_processor_factory'; import { getBatchEventProcessor } from './event_processor_factory'; import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; @@ -96,7 +102,7 @@ describe('createForwardingEventProcessor', () => { it('uses the browser default event dispatcher if none is provided', () => { const processor = createForwardingEventProcessor(); - + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, defaultEventDispatcher); }); @@ -146,6 +152,42 @@ describe('createBatchEventProcessor', () => { expect(transformSet('value')).toBe('value'); }); + it('should throw error if @react-native-async-storage/async-storage is not available', async () => { + isAsyncStorageAvailable = false; + const { AsyncStorageCache } = await vi.importActual< + typeof import('../utils/cache/async_storage_cache.react_native') + >('../utils/cache/async_storage_cache.react_native'); + + MockAsyncStorageCache.mockImplementationOnce(() => { + return new AsyncStorageCache(); + }); + + expect(() => createBatchEventProcessor({})).toThrowError( + 'Module not found: @react-native-async-storage/async-storage' + ); + + isAsyncStorageAvailable = true; + }); + + it('should not throw error if eventStore is provided and @react-native-async-storage/async-storage is not available', async () => { + isAsyncStorageAvailable = false; + const eventStore = { + operation: 'sync', + } as SyncCache<string>; + + const { AsyncStorageCache } = await vi.importActual< + typeof import('../utils/cache/async_storage_cache.react_native') + >('../utils/cache/async_storage_cache.react_native'); + + MockAsyncStorageCache.mockImplementationOnce(() => { + return new AsyncStorageCache(); + }); + + expect(() => createBatchEventProcessor({ eventStore })).not.toThrow(); + + isAsyncStorageAvailable = true; + }); + it('wraps the provided eventStore in a SyncPrefixCache if a SyncCache is provided as eventStore', () => { const eventStore = { operation: 'sync', @@ -153,7 +195,7 @@ describe('createBatchEventProcessor', () => { const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; @@ -172,7 +214,7 @@ describe('createBatchEventProcessor', () => { const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; @@ -184,7 +226,6 @@ describe('createBatchEventProcessor', () => { expect(transformSet({ value: 1 })).toBe('{"value":1}'); }); - it('uses the provided eventDispatcher', () => { const eventDispatcher = { dispatchEvent: vi.fn(), @@ -196,7 +237,7 @@ describe('createBatchEventProcessor', () => { }); it('uses the default browser event dispatcher if none is provided', () => { - const processor = createBatchEventProcessor({ }); + const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); }); @@ -210,7 +251,7 @@ describe('createBatchEventProcessor', () => { expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); - const processor2 = createBatchEventProcessor({ }); + const processor2 = createBatchEventProcessor({}); expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); }); @@ -220,7 +261,7 @@ describe('createBatchEventProcessor', () => { expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); - const processor2 = createBatchEventProcessor({ }); + const processor2 = createBatchEventProcessor({}); expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); }); @@ -230,19 +271,19 @@ describe('createBatchEventProcessor', () => { expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); - const processor2 = createBatchEventProcessor({ }); + const processor2 = createBatchEventProcessor({}); expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); }); it('uses maxRetries value of 5', () => { - const processor = createBatchEventProcessor({ }); + const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); }); it('uses the default failedEventRetryInterval', () => { - const processor = createBatchEventProcessor({ }); + const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); }); diff --git a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts index 80930cfb6..9529595be 100644 --- a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts +++ b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts @@ -14,27 +14,29 @@ * limitations under the License. */ -import AsyncStorage from '@react-native-async-storage/async-storage'; import PersistentKeyValueCache from './persistentKeyValueCache'; +import { getDefaultAsyncStorage } from '../../utils/import.react_native/@react-native-async-storage/async-storage'; export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { + private asyncStorage = getDefaultAsyncStorage(); + async contains(key: string): Promise<boolean> { - return await AsyncStorage.getItem(key) !== null; + return (await this.asyncStorage.getItem(key)) !== null; } async get(key: string): Promise<string | undefined> { - return (await AsyncStorage.getItem(key) || undefined); + return (await this.asyncStorage.getItem(key)) || undefined; } async remove(key: string): Promise<boolean> { if (await this.contains(key)) { - await AsyncStorage.removeItem(key); + await this.asyncStorage.removeItem(key); return true; } return false; } set(key: string, val: string): Promise<void> { - return AsyncStorage.setItem(key, val); + return this.asyncStorage.setItem(key, val); } } diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index a01b36c11..0ead808de 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -16,6 +16,26 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; +await vi.hoisted(async () => { + await mockRequireAsyncStorage(); +}); + +let isAsyncStorageAvailable = true; + +async function mockRequireAsyncStorage() { + const { Module } = await import('module'); + const M: any = Module; + + M._load_original = M._load; + M._load = (uri: string, parent: string) => { + if (uri === '@react-native-async-storage/async-storage') { + if (isAsyncStorageAvailable) return {}; + throw new Error('Module not found: @react-native-async-storage/async-storage'); + } + return M._load_original(uri, parent); + }; +} + vi.mock('./config_manager_factory', () => { return { getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), @@ -29,10 +49,10 @@ vi.mock('../utils/http_request_handler/browser_request_handler', () => { vi.mock('../plugins/key_value_cache/reactNativeAsyncStorageCache', () => { const ReactNativeAsyncStorageCache = vi.fn(); - return { 'default': ReactNativeAsyncStorageCache }; + return { default: ReactNativeAsyncStorageCache }; }); -import { getPollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; +import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.react_native'; import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; import ReactNativeAsyncStorageCache from '../plugins/key_value_cache/reactNativeAsyncStorageCache'; @@ -62,8 +82,14 @@ describe('createPollingConfigManager', () => { sdkKey: 'sdkKey', }; - const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0])).toBe(true); + createPollingProjectConfigManager(config); + + expect( + Object.is( + mockGetPollingConfigManager.mock.calls[0][0].requestHandler, + MockBrowserRequestHandler.mock.instances[0] + ) + ).toBe(true); }); it('uses uses autoUpdate = true by default', () => { @@ -71,7 +97,8 @@ describe('createPollingConfigManager', () => { sdkKey: 'sdkKey', }; - const projectConfigManager = createPollingProjectConfigManager(config); + createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); }); @@ -80,8 +107,11 @@ describe('createPollingConfigManager', () => { sdkKey: 'sdkKey', }; - const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockReactNativeAsyncStorageCache.mock.instances[0])).toBe(true); + createPollingProjectConfigManager(config); + + expect( + Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockReactNativeAsyncStorageCache.mock.instances[0]) + ).toBe(true); }); it('uses the provided options', () => { @@ -96,7 +126,48 @@ describe('createPollingConfigManager', () => { cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, }; - const projectConfigManager = createPollingProjectConfigManager(config); + createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); - }); + }); + + it('Should not throw error if a cache is present in the config, and async storage is not available', async () => { + isAsyncStorageAvailable = false; + const { default: ReactNativeAsyncStorageCache } = await vi.importActual< + typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') + >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + }; + + MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { + return new ReactNativeAsyncStorageCache(); + }); + + expect(() => createPollingProjectConfigManager(config)).not.toThrow(); + isAsyncStorageAvailable = true; + }); + + it('should throw an error if cache is not present in the config, and async storage is not available', async () => { + isAsyncStorageAvailable = false; + + const { default: ReactNativeAsyncStorageCache } = await vi.importActual< + typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') + >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + }; + + MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { + return new ReactNativeAsyncStorageCache(); + }); + + expect(() => createPollingProjectConfigManager(config)).toThrowError( + 'Module not found: @react-native-async-storage/async-storage' + ); + isAsyncStorageAvailable = true; + }); }); diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts index 6978ac61e..984f3c9d0 100644 --- a/lib/project_config/config_manager_factory.react_native.ts +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -23,7 +23,8 @@ export const createPollingProjectConfigManager = (config: PollingConfigManagerCo const defaultConfig = { autoUpdate: true, requestHandler: new BrowserRequestHandler(), - cache: new ReactNativeAsyncStorageCache(), + cache: config.cache || new ReactNativeAsyncStorageCache() }; + return getPollingConfigManager({ ...defaultConfig, ...config }); }; diff --git a/lib/utils/cache/async_storage_cache.react_native.spec.ts b/lib/utils/cache/async_storage_cache.react_native.spec.ts index d1a7954e4..f67fca7bf 100644 --- a/lib/utils/cache/async_storage_cache.react_native.spec.ts +++ b/lib/utils/cache/async_storage_cache.react_native.spec.ts @@ -1,4 +1,3 @@ - /** * Copyright 2022-2024, Optimizely * @@ -15,62 +14,41 @@ * limitations under the License. */ -vi.mock('@react-native-async-storage/async-storage', () => { - const MockAsyncStorage = { - data: new Map<string, any>(), - async setItem(key: string, value: string) { - this.data.set(key, value); - }, - async getItem(key: string) { - return this.data.get(key) || null; - }, - async removeItem(key: string) { - this.data.delete(key); - }, - async getAllKeys() { - return Array.from(this.data.keys()); - }, - async clear() { - this.data.clear(); - }, - async multiGet(keys: string[]) { - return keys.map(key => [key, this.data.get(key)]); - }, - } - return { default: MockAsyncStorage }; -}); - -import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { vi, describe, it, expect } from 'vitest'; import { AsyncStorageCache } from './async_storage_cache.react_native'; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { getDefaultAsyncStorage } from '../import.react_native/@react-native-async-storage/async-storage'; + +vi.mock('@react-native-async-storage/async-storage'); type TestData = { a: number; b: string; d: { e: boolean }; -} - +}; describe('AsyncStorageCache', () => { - beforeEach(async () => { - await AsyncStorage.clear(); - }); + const asyncStorage = getDefaultAsyncStorage(); - it('should store a stringified value in asyncstorage', async () => { + it('should store a stringified value in async storage', async () => { const cache = new AsyncStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; await cache.set('key', data); - expect(await AsyncStorage.getItem('key')).toBe(JSON.stringify(data)); + + expect(await asyncStorage.getItem('key')).toBe(JSON.stringify(data)); + expect(await cache.get('key')).toEqual(data); }); it('should return undefined if get is called for a nonexistent key', async () => { const cache = new AsyncStorageCache<string>(); + expect(await cache.get('nonexistent')).toBeUndefined(); }); it('should return the value if get is called for an existing key', async () => { const cache = new AsyncStorageCache<string>(); await cache.set('key', 'value'); + expect(await cache.get('key')).toBe('value'); }); @@ -78,6 +56,7 @@ describe('AsyncStorageCache', () => { const cache = new AsyncStorageCache<TestData>(); const data = { a: 1, b: '2', d: { e: true } }; await cache.set('key', data); + expect(await cache.get('key')).toEqual(data); }); @@ -85,22 +64,25 @@ describe('AsyncStorageCache', () => { const cache = new AsyncStorageCache<string>(); await cache.set('key', 'value'); await cache.remove('key'); - expect(await AsyncStorage.getItem('key')).toBeNull(); + + expect(await asyncStorage.getItem('key')).toBeNull(); }); it('should remove all keys from async storage when clear is called', async () => { const cache = new AsyncStorageCache<string>(); await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); - expect((await AsyncStorage.getAllKeys()).length).toBe(2); + + expect((await asyncStorage.getAllKeys()).length).toBe(2); cache.clear(); - expect((await AsyncStorage.getAllKeys()).length).toBe(0); + expect((await asyncStorage.getAllKeys()).length).toBe(0); }); it('should return all keys when getKeys is called', async () => { const cache = new AsyncStorageCache<string>(); await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); + expect(await cache.getKeys()).toEqual(['key1', 'key2']); }); @@ -108,6 +90,7 @@ describe('AsyncStorageCache', () => { const cache = new AsyncStorageCache<string>(); await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); + expect(await cache.getBatched(['key1', 'key2'])).toEqual(['value1', 'value2']); }); }); diff --git a/lib/utils/cache/async_storage_cache.react_native.ts b/lib/utils/cache/async_storage_cache.react_native.ts index 529287a6c..4656496d2 100644 --- a/lib/utils/cache/async_storage_cache.react_native.ts +++ b/lib/utils/cache/async_storage_cache.react_native.ts @@ -16,34 +16,35 @@ import { Maybe } from "../type"; import { AsyncCache } from "./cache"; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { getDefaultAsyncStorage } from "../import.react_native/@react-native-async-storage/async-storage"; export class AsyncStorageCache<V> implements AsyncCache<V> { public readonly operation = 'async'; + private asyncStorage = getDefaultAsyncStorage(); async get(key: string): Promise<V | undefined> { - const value = await AsyncStorage.getItem(key); + const value = await this.asyncStorage.getItem(key); return value ? JSON.parse(value) : undefined; } async remove(key: string): Promise<unknown> { - return AsyncStorage.removeItem(key); + return this.asyncStorage.removeItem(key); } async set(key: string, val: V): Promise<unknown> { - return AsyncStorage.setItem(key, JSON.stringify(val)); + return this.asyncStorage.setItem(key, JSON.stringify(val)); } async clear(): Promise<unknown> { - return AsyncStorage.clear(); + return this.asyncStorage.clear(); } async getKeys(): Promise<string[]> { - return [... await AsyncStorage.getAllKeys()]; + return [... await this.asyncStorage.getAllKeys()]; } async getBatched(keys: string[]): Promise<Maybe<V>[]> { - const items = await AsyncStorage.multiGet(keys); + const items = await this.asyncStorage.multiGet(keys); return items.map(([key, value]) => value ? JSON.parse(value) : undefined); } } diff --git a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts new file mode 100644 index 000000000..78deb7f2d --- /dev/null +++ b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { AsyncStorageStatic } from '@react-native-async-storage/async-storage' + +export const getDefaultAsyncStorage = (): AsyncStorageStatic => { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('@react-native-async-storage/async-storage').default; + } catch (e) { + throw new Error('Module not found: @react-native-async-storage/async-storage'); + } +}; diff --git a/tests/reactNativeAsyncStorageCache.spec.ts b/tests/reactNativeAsyncStorageCache.spec.ts index a7d1a936e..559c1f071 100644 --- a/tests/reactNativeAsyncStorageCache.spec.ts +++ b/tests/reactNativeAsyncStorageCache.spec.ts @@ -14,25 +14,19 @@ * limitations under the License. */ -import { describe, beforeEach, beforeAll, it, vi, expect } from 'vitest'; - -vi.mock('@react-native-async-storage/async-storage'); - +import { describe, beforeEach, it, vi, expect } from 'vitest'; import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'; + +vi.mock('@react-native-async-storage/async-storage') describe('ReactNativeAsyncStorageCache', () => { const TEST_OBJECT_KEY = 'testObject'; const testObject = { name: 'An object', with: { some: 2, properties: ['one', 'two'] } }; let cacheInstance: ReactNativeAsyncStorageCache; - beforeAll(() => { - cacheInstance = new ReactNativeAsyncStorageCache(); - }); - beforeEach(() => { - AsyncStorage.clearStore(); - AsyncStorage.setItem(TEST_OBJECT_KEY, JSON.stringify(testObject)); + cacheInstance = new ReactNativeAsyncStorageCache(); + cacheInstance.set(TEST_OBJECT_KEY, JSON.stringify(testObject)); }); describe('contains', () => { @@ -77,16 +71,4 @@ describe('ReactNativeAsyncStorageCache', () => { expect(wasSuccessful).toBe(false); }); }); - - describe('set', () => { - it('should resolve promise if item was successfully set in the cache', async () => { - const anotherTestStringValue = 'This should be found too.'; - - await cacheInstance.set('anotherTestStringValue', anotherTestStringValue); - - const itemsInReactAsyncStorage = AsyncStorage.dumpItems(); - expect(itemsInReactAsyncStorage['anotherTestStringValue']).toEqual(anotherTestStringValue); - expect(itemsInReactAsyncStorage[TEST_OBJECT_KEY]).toEqual(JSON.stringify(testObject)); - }); - }); }); From c2880e976a7eef200823718a9ded7fe74acc9e30 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 11 Dec 2024 18:31:19 +0600 Subject: [PATCH 102/200] [FSSDK-10985] Implement log message file generator (#974) --- .gitignore | 4 +++- message_generator.ts | 36 ++++++++++++++++++++++++++++++++++++ package-lock.json | 10 ++++++++++ package.json | 4 +++- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 message_generator.ts diff --git a/.gitignore b/.gitignore index 5431dd3d9..4ab687ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ dist/ .DS_STORE browserstack.err -local.log \ No newline at end of file +local.log + +**/*.gen.ts diff --git a/message_generator.ts b/message_generator.ts new file mode 100644 index 000000000..2c7cd53b1 --- /dev/null +++ b/message_generator.ts @@ -0,0 +1,36 @@ +import path from 'path'; +import { writeFile } from 'fs/promises'; + +const generate = async () => { + const inp = process.argv.slice(2); + for(const filePath of inp) { + console.log('generating messages for: ', filePath); + const parsedPath = path.parse(filePath); + const fileName = parsedPath.name; + const dirName = parsedPath.dir; + const ext = parsedPath.ext; + + const genFilePath = path.join(dirName, `${fileName}.gen${ext}`); + console.log('generated file path: ', genFilePath); + const exports = await import(filePath); + const messages : Array<any> = []; + + let genOut = ''; + + Object.keys(exports).forEach((key, i) => { + const msg = exports[key]; + genOut += `export const ${key} = '${i}';\n`; + messages.push(exports[key]) + }); + + genOut += `export const messages = ${JSON.stringify(messages, null, 2)};` + await writeFile(genFilePath, genOut, 'utf-8'); + }; +} + +generate().then(() => { + console.log('successfully generated messages'); +}).catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index a1588ec80..e37a742e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", "happy-dom": "^14.12.3", + "jiti": "^2.4.1", "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", @@ -10958,6 +10959,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.1.tgz", + "integrity": "sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/joi": { "version": "17.13.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", diff --git a/package.json b/package.json index 2d2e09618..dbb2bf109 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,8 @@ "coveralls": "nyc --reporter=lcov npm test", "prepare": "npm run build", "prepublishOnly": "npm test && npm run test-ci", - "postbuild:win": "@powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.min.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.min.d.ts\"" + "postbuild:win": "@powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.min.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.min.d.ts\"", + "genmsg": "jiti message_generator ./lib/error_messages.ts" }, "repository": { "type": "git", @@ -132,6 +133,7 @@ "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", "happy-dom": "^14.12.3", + "jiti": "^2.4.1", "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", From e119925352056ada7b41ca4283ad1da2332a6304 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 11 Dec 2024 23:54:01 +0600 Subject: [PATCH 103/200] [FSSDK-10989] refactor notification center using event emitter (#975) --- lib/export_types.ts | 3 +- lib/notification_center/index.tests.js | 322 +++++++++------------ lib/notification_center/index.ts | 214 +++++--------- lib/notification_center/type.ts | 80 +++++ lib/optimizely/index.tests.js | 67 ++--- lib/optimizely/index.ts | 17 +- lib/optimizely_user_context/index.tests.js | 2 +- lib/shared_types.ts | 20 +- lib/utils/enums/index.ts | 50 +--- lib/utils/event_emitter/event_emitter.ts | 4 + 10 files changed, 342 insertions(+), 437 deletions(-) create mode 100644 lib/notification_center/type.ts diff --git a/lib/export_types.ts b/lib/export_types.ts index 759bb86c0..df11a89a8 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,6 @@ export { ListenerPayload, OptimizelyDecision, OptimizelyUserContext, - NotificationListener, Config, Client, ActivateListenerPayload, diff --git a/lib/notification_center/index.tests.js b/lib/notification_center/index.tests.js index e1459af41..2a398c4cf 100644 --- a/lib/notification_center/index.tests.js +++ b/lib/notification_center/index.tests.js @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2020, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2020, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import sinon from 'sinon'; import { assert } from 'chai'; @@ -20,6 +20,7 @@ import { createNotificationCenter } from './'; import * as enums from '../utils/enums'; import { createLogger } from '../plugins/logger'; import errorHandler from '../plugins/error_handler'; +import { NOTIFICATION_TYPES } from './type'; var LOG_LEVEL = enums.LOG_LEVEL; @@ -62,47 +63,6 @@ describe('lib/core/notification_center', function() { }); context('the listener type is a valid type', function() { - it('should return -1 if that same callback is already added', function() { - var activateCallback; - var decisionCallback; - var logEventCallback; - var configUpdateCallback; - var trackCallback; - // add a listener for each type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallback); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallback); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallback); - notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, - configUpdateCallback - ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallback); - // assertions - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallback), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallback), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallback), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, - configUpdateCallback - ), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallback), - -1 - ); - }); - it('should return an id (listenerId) > 0 of the notification listener if callback is not already added', function() { var activateCallback; var decisionCallback; @@ -111,23 +71,23 @@ describe('lib/core/notification_center', function() { var trackCallback; // store a listenerId for each type var activateListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateCallback ); var decisionListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionCallback ); var logEventListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, logEventCallback ); var configUpdateListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallback ); var trackListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackCallback ); // assertions @@ -157,23 +117,23 @@ describe('lib/core/notification_center', function() { var trackCallback; // add listeners for each type var activateListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateCallback ); var decisionListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionCallback ); var logEventListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, logEventCallback ); var configListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallback ); var trackListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackCallback ); // remove listeners for each type @@ -204,34 +164,34 @@ describe('lib/core/notification_center', function() { var trackCallbackSpy2 = sinon.spy(); // register listeners for each type var activateListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1 ); var decisionListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1 ); var logeventlistenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1 ); var configUpdateListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); var trackListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackCallbackSpy1 ); // register second listeners for each type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy2 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); // remove first listener var activateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(activateListenerId1); var decisionListenerRemoved1 = notificationCenterInstance.removeNotificationListener(decisionListenerId1); @@ -241,11 +201,11 @@ describe('lib/core/notification_center', function() { ); var trackListenerRemoved1 = notificationCenterInstance.removeNotificationListener(trackListenerId1); // send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // Assertions assert.strictEqual(activateListenerRemoved1, true); sinon.assert.notCalled(activateCallbackSpy1); @@ -274,22 +234,22 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove all listeners notificationCenterInstance.clearAllNotificationListeners(); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that none of the now removed listeners were called sinon.assert.notCalled(activateCallbackSpy1); sinon.assert.notCalled(decisionCallbackSpy1); @@ -305,12 +265,12 @@ describe('lib/core/notification_center', function() { var activateCallbackSpy1 = sinon.spy(); var activateCallbackSpy2 = sinon.spy(); //add 2 different listeners for ACTIVATE - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); // remove ACTIVATE listeners - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.ACTIVATE); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); // check that none of the ACTIVATE listeners were called sinon.assert.notCalled(activateCallbackSpy1); sinon.assert.notCalled(activateCallbackSpy2); @@ -320,12 +280,12 @@ describe('lib/core/notification_center', function() { var decisionCallbackSpy1 = sinon.spy(); var decisionCallbackSpy2 = sinon.spy(); //add 2 different listeners for DECISION - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); // remove DECISION listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.DECISION); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.DECISION); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); // check that none of the DECISION listeners were called sinon.assert.notCalled(decisionCallbackSpy1); sinon.assert.notCalled(decisionCallbackSpy2); @@ -335,12 +295,12 @@ describe('lib/core/notification_center', function() { var logEventCallbackSpy1 = sinon.spy(); var logEventCallbackSpy2 = sinon.spy(); //add 2 different listeners for LOG_EVENT - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); // remove LOG_EVENT listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.LOG_EVENT); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); // check that none of the LOG_EVENT listeners were called sinon.assert.notCalled(logEventCallbackSpy1); sinon.assert.notCalled(logEventCallbackSpy2); @@ -351,17 +311,17 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy2 = sinon.spy(); //add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy2 ); // remove OPTIMIZELY_CONFIG_UPDATE listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); // check that none of the OPTIMIZELY_CONFIG_UPDATE listeners were called sinon.assert.notCalled(configUpdateCallbackSpy1); sinon.assert.notCalled(configUpdateCallbackSpy2); @@ -371,12 +331,12 @@ describe('lib/core/notification_center', function() { var trackCallbackSpy1 = sinon.spy(); var trackCallbackSpy2 = sinon.spy(); //add 2 different listeners for TRACK - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); // remove TRACK listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.TRACK); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.TRACK); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that none of the TRACK listeners were called sinon.assert.notCalled(trackCallbackSpy1); sinon.assert.notCalled(trackCallbackSpy2); @@ -392,24 +352,24 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); //add 2 different listeners for ACTIVATE - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only ACTIVATE type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.ACTIVATE); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that ACTIVATE listeners were note called sinon.assert.notCalled(activateCallbackSpy1); sinon.assert.notCalled(activateCallbackSpy2); @@ -428,24 +388,24 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); // add 2 different listeners for DECISION - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only DECISION type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.DECISION); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that DECISION listeners were not called sinon.assert.notCalled(decisionCallbackSpy1); sinon.assert.notCalled(decisionCallbackSpy2); @@ -464,24 +424,24 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); // add 2 different listeners for LOG_EVENT - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only LOG_EVENT type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.LOG_EVENT); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that LOG_EVENT listeners were not called sinon.assert.notCalled(logEventCallbackSpy1); sinon.assert.notCalled(logEventCallbackSpy2); @@ -501,26 +461,26 @@ describe('lib/core/notification_center', function() { var trackCallbackSpy1 = sinon.spy(); // add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy2 ); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only OPTIMIZELY_CONFIG_UPDATE type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that OPTIMIZELY_CONFIG_UPDATE listeners were not called sinon.assert.notCalled(configUpdateCallbackSpy1); sinon.assert.notCalled(configUpdateCallbackSpy2); @@ -539,24 +499,24 @@ describe('lib/core/notification_center', function() { var logEventCallbackSpy1 = sinon.spy(); var configUpdateCallbackSpy1 = sinon.spy(); // add 2 different listeners for TRACK - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); // remove only TRACK type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.TRACK); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that TRACK listeners were not called sinon.assert.notCalled(trackCallbackSpy1); sinon.assert.notCalled(trackCallbackSpy2); @@ -604,23 +564,23 @@ describe('lib/core/notification_center', function() { eventTags: {}, }; // add listeners - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, activateData); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, decisionData); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, activateData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, decisionData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, logEventData); notificationCenterInstance.sendNotifications( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateData ); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, trackData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, trackData); // assertions sinon.assert.calledWithExactly(activateCallbackSpy1, activateData); sinon.assert.calledWithExactly(decisionCallbackSpy1, decisionData); diff --git a/lib/notification_center/index.ts b/lib/notification_center/index.ts index ee2135104..d33c3fa2e 100644 --- a/lib/notification_center/index.ts +++ b/lib/notification_center/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020, 2022, Optimizely + * Copyright 2020, 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,29 +15,38 @@ */ import { LogHandler, ErrorHandler } from '../modules/logging'; import { objectValues } from '../utils/fns'; -import { NotificationListener, ListenerPayload } from '../shared_types'; import { LOG_LEVEL, LOG_MESSAGES, - NOTIFICATION_TYPES, } from '../utils/enums'; +import { NOTIFICATION_TYPES } from './type'; +import { NotificationType, NotificationPayload } from './type'; +import { Consumer, Fn } from '../utils/type'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; + const MODULE_NAME = 'NOTIFICATION_CENTER'; interface NotificationCenterOptions { logger: LogHandler; errorHandler: ErrorHandler; } - -interface ListenerEntry { - id: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callback: (notificationData: any) => void; +export interface NotificationCenter { + addNotificationListener<N extends NotificationType>( + notificationType: N, + callback: Consumer<NotificationPayload[N]> + ): number + removeNotificationListener(listenerId: number): boolean; + clearAllNotificationListeners(): void; + clearNotificationListeners(notificationType: NotificationType): void; } -type NotificationListeners = { - [key: string]: ListenerEntry[]; +export interface NotificationSender { + sendNotifications<N extends NotificationType>( + notificationType: N, + notificationData: NotificationPayload[N] + ): void; } /** @@ -46,11 +55,13 @@ type NotificationListeners = { * - ACTIVATE: An impression event will be sent to Optimizely. * - TRACK a conversion event will be sent to Optimizely */ -export class NotificationCenter { +export class DefaultNotificationCenter implements NotificationCenter, NotificationSender { private logger: LogHandler; private errorHandler: ErrorHandler; - private notificationListeners: NotificationListeners; - private listenerId: number; + + private removerId = 1; + private eventEmitter: EventEmitter<NotificationPayload> = new EventEmitter(); + private removers: Map<number, Fn> = new Map(); /** * @constructor @@ -61,13 +72,6 @@ export class NotificationCenter { constructor(options: NotificationCenterOptions) { this.logger = options.logger; this.errorHandler = options.errorHandler; - this.notificationListeners = {}; - objectValues(NOTIFICATION_TYPES).forEach( - (notificationTypeEnum) => { - this.notificationListeners[notificationTypeEnum] = []; - } - ); - this.listenerId = 1; } /** @@ -80,47 +84,40 @@ export class NotificationCenter { * can happen if the first argument is not a valid notification type, or if the same callback * function was already added as a listener by a prior call to this function. */ - addNotificationListener<T extends ListenerPayload>( - notificationType: string, - callback: NotificationListener<T> + addNotificationListener<N extends NotificationType>( + notificationType: N, + callback: Consumer<NotificationPayload[N]> ): number { - try { - const notificationTypeValues: string[] = objectValues(NOTIFICATION_TYPES); - const isNotificationTypeValid = notificationTypeValues.indexOf(notificationType) > -1; - if (!isNotificationTypeValid) { - return -1; - } - - if (!this.notificationListeners[notificationType]) { - this.notificationListeners[notificationType] = []; - } - - let callbackAlreadyAdded = false; - (this.notificationListeners[notificationType] || []).forEach( - (listenerEntry) => { - if (listenerEntry.callback === callback) { - callbackAlreadyAdded = true; - return; - } - }); - - if (callbackAlreadyAdded) { - return -1; - } - - this.notificationListeners[notificationType].push({ - id: this.listenerId, - callback: callback, - }); - - const returnId = this.listenerId; - this.listenerId += 1; - return returnId; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + const notificationTypeValues: string[] = objectValues(NOTIFICATION_TYPES); + const isNotificationTypeValid = notificationTypeValues.indexOf(notificationType) > -1; + if (!isNotificationTypeValid) { return -1; } + + const returnId = this.removerId++; + const remover = this.eventEmitter.on( + notificationType, this.wrapWithErrorHandling(notificationType, callback)); + this.removers.set(returnId, remover); + return returnId; + } + + private wrapWithErrorHandling<N extends NotificationType>( + notificationType: N, + callback: Consumer<NotificationPayload[N]> + ): Consumer<NotificationPayload[N]> { + return (notificationData: NotificationPayload[N]) => { + try { + callback(notificationData); + } catch (ex: any) { + this.logger.log( + LOG_LEVEL.ERROR, + LOG_MESSAGES.NOTIFICATION_LISTENER_EXCEPTION, + MODULE_NAME, + notificationType, + ex.message, + ); + } + }; } /** @@ -130,103 +127,40 @@ export class NotificationCenter { * otherwise. */ removeNotificationListener(listenerId: number): boolean { - try { - let indexToRemove: number | undefined; - let typeToRemove: string | undefined; - - Object.keys(this.notificationListeners).some( - (notificationType) => { - const listenersForType = this.notificationListeners[notificationType]; - (listenersForType || []).every((listenerEntry, i) => { - if (listenerEntry.id === listenerId) { - indexToRemove = i; - typeToRemove = notificationType; - return false; - } - - return true; - }); - - if (indexToRemove !== undefined && typeToRemove !== undefined) { - return true; - } - - return false; - } - ); - - if (indexToRemove !== undefined && typeToRemove !== undefined) { - this.notificationListeners[typeToRemove].splice(indexToRemove, 1); - return true; - } - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + const remover = this.removers.get(listenerId); + if (remover) { + remover(); + return true; } - - return false; + return false } /** * Removes all previously added notification listeners, for all notification types */ clearAllNotificationListeners(): void { - try { - objectValues(NOTIFICATION_TYPES).forEach( - (notificationTypeEnum) => { - this.notificationListeners[notificationTypeEnum] = []; - } - ); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - } + this.eventEmitter.removeAllListeners(); } /** * Remove all previously added notification listeners for the argument type - * @param {NOTIFICATION_TYPES} notificationType One of NOTIFICATION_TYPES + * @param {NotificationType} notificationType One of NotificationType */ - clearNotificationListeners(notificationType: NOTIFICATION_TYPES): void { - try { - this.notificationListeners[notificationType] = []; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - } + clearNotificationListeners(notificationType: NotificationType): void { + this.eventEmitter.removeListeners(notificationType); } /** * Fires notifications for the argument type. All registered callbacks for this type will be * called. The notificationData object will be passed on to callbacks called. - * @param {string} notificationType One of NOTIFICATION_TYPES + * @param {NotificationType} notificationType One of NotificationType * @param {Object} notificationData Will be passed to callbacks called */ - sendNotifications<T extends ListenerPayload>( - notificationType: string, - notificationData?: T + sendNotifications<N extends NotificationType>( + notificationType: N, + notificationData: NotificationPayload[N] ): void { - try { - (this.notificationListeners[notificationType] || []).forEach( - (listenerEntry) => { - const callback = listenerEntry.callback; - try { - callback(notificationData); - } catch (ex: any) { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.NOTIFICATION_LISTENER_EXCEPTION, - MODULE_NAME, - notificationType, - ex.message, - ); - } - } - ); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - } + this.eventEmitter.emit(notificationType, notificationData); } } @@ -235,12 +169,6 @@ export class NotificationCenter { * @param {NotificationCenterOptions} options * @returns {NotificationCenter} An instance of NotificationCenter */ -export function createNotificationCenter(options: NotificationCenterOptions): NotificationCenter { - return new NotificationCenter(options); -} - -export interface NotificationSender { - // TODO[OASIS-6649]: Don't use any type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData?: any): void +export function createNotificationCenter(options: NotificationCenterOptions): DefaultNotificationCenter { + return new DefaultNotificationCenter(options); } diff --git a/lib/notification_center/type.ts b/lib/notification_center/type.ts new file mode 100644 index 000000000..7dcc132ab --- /dev/null +++ b/lib/notification_center/type.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogEvent } from '../event_processor/event_dispatcher/event_dispatcher'; +import { EventTags, Experiment, UserAttributes, Variation } from '../shared_types'; + +export type UserEventListenerPayload = { + userId: string; + attributes?: UserAttributes; +} + +export type ActivateListenerPayload = UserEventListenerPayload & { + experiment: Experiment | null; + variation: Variation | null; + logEvent: LogEvent; +} + +export type TrackListenerPayload = UserEventListenerPayload & { + eventKey: string; + eventTags?: EventTags; + logEvent: LogEvent; +} + +export const DECISION_NOTIFICATION_TYPES = { + AB_TEST: 'ab-test', + FEATURE: 'feature', + FEATURE_TEST: 'feature-test', + FEATURE_VARIABLE: 'feature-variable', + ALL_FEATURE_VARIABLES: 'all-feature-variables', + FLAG: 'flag', +} as const; + +export type DecisionNotificationType = typeof DECISION_NOTIFICATION_TYPES[keyof typeof DECISION_NOTIFICATION_TYPES]; + +// TODO: Add more specific types for decision info +export type OptimizelyDecisionInfo = Record<string, any>; + +export type DecisionListenerPayload = UserEventListenerPayload & { + type: DecisionNotificationType; + decisionInfo: OptimizelyDecisionInfo; +} + +export type LogEventListenerPayload = LogEvent; + +export type OptimizelyConfigUpdateListenerPayload = undefined; + +export type NotificationPayload = { + ACTIVATE: ActivateListenerPayload; + DECISION: DecisionListenerPayload; + TRACK: TrackListenerPayload; + LOG_EVENT: LogEventListenerPayload; + OPTIMIZELY_CONFIG_UPDATE: OptimizelyConfigUpdateListenerPayload; +}; + +export type NotificationType = keyof NotificationPayload; + +export type NotificationTypeValues = { + [key in NotificationType]: key; +} + +export const NOTIFICATION_TYPES: NotificationTypeValues = { + ACTIVATE: 'ACTIVATE', + DECISION: 'DECISION', + LOG_EVENT: 'LOG_EVENT', + OPTIMIZELY_CONFIG_UPDATE: 'OPTIMIZELY_CONFIG_UPDATE', + TRACK: 'TRACK', +}; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index e7fc378f7..187764625 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -16,7 +16,7 @@ import { assert, expect } from 'chai'; import sinon from 'sinon'; import { sprintf } from '../utils/fns'; -import { NOTIFICATION_TYPES } from '../utils/enums'; +import { NOTIFICATION_TYPES } from '../notification_center/type'; import * as logging from '../modules/logging'; import Optimizely from './'; @@ -37,13 +37,13 @@ import { getForwardingEventProcessor } from '../event_processor/forwarding_event import { createNotificationCenter } from '../notification_center'; import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import { DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; var LOG_MESSAGES = enums.LOG_MESSAGES; var DECISION_SOURCES = enums.DECISION_SOURCES; var DECISION_MESSAGES = enums.DECISION_MESSAGES; -var DECISION_NOTIFICATION_TYPES = enums.DECISION_NOTIFICATION_TYPES; var FEATURE_VARIABLE_TYPES = enums.FEATURE_VARIABLE_TYPES; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); @@ -2316,14 +2316,14 @@ describe('lib/optimizely', function() { }); it('should call a listener added for activate when activate is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); var variationKey = optlyInstance.activate('testExperiment', 'testUser'); assert.strictEqual(variationKey, 'variation'); sinon.assert.calledOnce(activateListener); }); it('should call a listener added for track when track is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); sinon.assert.calledOnce(trackListener); @@ -2331,7 +2331,7 @@ describe('lib/optimizely', function() { it('should not call a removed activate listener when activate is called', function() { var listenerId = optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateListener ); optlyInstance.notificationCenter.removeNotificationListener(listenerId); @@ -2342,7 +2342,7 @@ describe('lib/optimizely', function() { it('should not call a removed track listener when track is called', function() { var listenerId = optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackListener ); optlyInstance.notificationCenter.removeNotificationListener(listenerId); @@ -2352,9 +2352,9 @@ describe('lib/optimizely', function() { }); it('removeNotificationListener should only remove the listener with the argument ID', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); var trackListenerId = optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackListener ); optlyInstance.notificationCenter.removeNotificationListener(trackListenerId); @@ -2364,8 +2364,8 @@ describe('lib/optimizely', function() { }); it('should clear all notification listeners when clearAllNotificationListeners is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.notificationCenter.clearAllNotificationListeners(); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); @@ -2375,9 +2375,9 @@ describe('lib/optimizely', function() { }); it('should clear listeners of certain notification type when clearNotificationListeners is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); - optlyInstance.notificationCenter.clearNotificationListeners(enums.NOTIFICATION_TYPES.ACTIVATE); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); @@ -2385,13 +2385,6 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(trackListener); }); - it('should only call the listener once after the same listener was added twice', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.activate('testExperiment', 'testUser'); - sinon.assert.calledOnce(activateListener); - }); - it('should not add a listener with an invalid type argument', function() { var listenerId = optlyInstance.notificationCenter.addNotificationListener( 'not a notification type', @@ -2405,16 +2398,16 @@ describe('lib/optimizely', function() { }); it('should call multiple notification listeners for activate when activate is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener2); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener2); optlyInstance.activate('testExperiment', 'testUser'); sinon.assert.calledOnce(activateListener); sinon.assert.calledOnce(activateListener2); }); it('should call multiple notification listeners for track when track is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener2); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener2); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); sinon.assert.calledOnce(trackListener); @@ -2422,7 +2415,7 @@ describe('lib/optimizely', function() { }); it('should pass the correct arguments to an activate listener when activate is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); optlyInstance.activate('testExperiment', 'testUser'); var expectedImpressionEvent = { httpVerb: 'POST', @@ -2484,7 +2477,7 @@ describe('lib/optimizely', function() { var attributes = { browser_type: 'firefox', }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); optlyInstance.activate('testExperiment', 'testUser', attributes); var expectedImpressionEvent = { httpVerb: 'POST', @@ -2550,7 +2543,7 @@ describe('lib/optimizely', function() { }); it('should pass the correct arguments to a track listener when track is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); var expectedConversionEvent = { @@ -2598,7 +2591,7 @@ describe('lib/optimizely', function() { var attributes = { browser_type: 'firefox', }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser', attributes); optlyInstance.track('testEvent', 'testUser', attributes); var expectedConversionEvent = { @@ -2657,7 +2650,7 @@ describe('lib/optimizely', function() { value: 1.234, non_revenue: 'abc', }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser', attributes); optlyInstance.track('testEvent', 'testUser', attributes, eventTags); var expectedConversionEvent = { @@ -2738,7 +2731,7 @@ describe('lib/optimizely', function() { }); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionListener ); }); @@ -2802,7 +2795,7 @@ describe('lib/optimizely', function() { }); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionListener ); }); @@ -2857,7 +2850,7 @@ describe('lib/optimizely', function() { notificationCenter, }); - optly.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionListener); + optly.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionListener); fakeDecisionResponse = { result: '594099', @@ -2899,7 +2892,7 @@ describe('lib/optimizely', function() { }); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionListener ); }); @@ -6915,7 +6908,7 @@ describe('lib/optimizely', function() { var decisionListener = sinon.spy(); var attributes = { test_attribute: 'test_value' }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionListener); var result = optlyInstance.getEnabledFeatures('test_user', attributes); assert.strictEqual(result.length, 5); assert.deepEqual(result, [ @@ -10163,7 +10156,7 @@ describe('lib/optimizely', function() { it('emits a notification when the project config manager emits a new project config object', function() { var listener = sinon.spy(); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, listener ); var newConfig = createProjectConfig(testData.getTestProjectConfigWithFeatures()); @@ -10214,7 +10207,7 @@ describe('lib/optimizely', function() { it('should trigger a log event notification when an impression event is dispatched', function() { var notificationListener = sinon.spy(); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, notificationListener ); fakeDecisionResponse = { @@ -10232,7 +10225,7 @@ describe('lib/optimizely', function() { it('should trigger a log event notification when a conversion event is dispatched', function() { var notificationListener = sinon.spy(); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, notificationListener ); optlyInstance.track('testEvent', 'testUser'); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 96ef632f3..7628a0a17 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -16,7 +16,7 @@ import { LoggerFacade, ErrorHandler } from '../modules/logging'; import { sprintf, objectValues } from '../utils/fns'; -import { NotificationCenter } from '../notification_center'; +import { DefaultNotificationCenter, NotificationCenter } from '../notification_center'; import { EventProcessor } from '../event_processor/event_processor'; import { IOdpManager } from '../odp/odp_manager'; @@ -59,8 +59,8 @@ import { DECISION_SOURCES, DECISION_MESSAGES, FEATURE_VARIABLE_TYPES, - DECISION_NOTIFICATION_TYPES, - NOTIFICATION_TYPES, + // DECISION_NOTIFICATION_TYPES, + // NOTIFICATION_TYPES, NODE_CLIENT_ENGINE, CLIENT_VERSION, ODP_DEFAULT_EVENT_TYPE, @@ -69,7 +69,8 @@ import { } from '../utils/enums'; import { Fn } from '../utils/type'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; -import { time } from 'console'; + +import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; const MODULE_NAME = 'OPTIMIZELY'; @@ -99,7 +100,7 @@ export default class Optimizely implements Client { private eventProcessor?: EventProcessor; private defaultDecideOptions: { [key: string]: boolean }; protected odpManager?: IOdpManager; - public notificationCenter: NotificationCenter; + public notificationCenter: DefaultNotificationCenter; constructor(config: OptimizelyOptions) { let clientEngine = config.clientEngine; @@ -142,7 +143,7 @@ export default class Optimizely implements Client { configObj.projectId ); - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, undefined); this.updateOdpSettings(); }); @@ -178,7 +179,7 @@ export default class Optimizely implements Client { Promise.resolve(undefined); this.eventProcessor?.onDispatch((event) => { - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, event as any); + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, event); }); this.readyPromise = Promise.all([ @@ -415,7 +416,7 @@ export default class Optimizely implements Client { experiment, this.createInternalUserContext(userId, attributes) as OptimizelyUserContext ).result; - const decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) + const decisionNotificationType: DecisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) ? DECISION_NOTIFICATION_TYPES.FEATURE_TEST : DECISION_NOTIFICATION_TYPES.AB_TEST; diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index a895d928d..fc72ffe0e 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import * as logging from '../modules/logging'; import { sprintf } from '../utils/fns'; -import { NOTIFICATION_TYPES } from '../utils/enums'; +import { NOTIFICATION_TYPES } from '../notification_center/type'; import OptimizelyUserContext from './'; import { createLogger } from '../plugins/logger'; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index f021124ab..2cab1c052 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -21,8 +21,7 @@ import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; -import { NotificationCenter as NotificationCenterImpl } from './notification_center'; -import { NOTIFICATION_TYPES } from './utils/enums'; +import { NotificationCenter, DefaultNotificationCenter } from './notification_center'; import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; @@ -43,6 +42,8 @@ import { EventProcessor } from './event_processor/event_processor'; export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; export { EventProcessor } from './event_processor/event_processor'; +export { NotificationCenter } from './notification_center'; + export interface BucketerParams { experimentId: string; experimentKey: string; @@ -120,19 +121,6 @@ export interface ListenerPayload { attributes?: UserAttributes; } -export type NotificationListener<T extends ListenerPayload> = (notificationData: T) => void; - -// NotificationCenter-related types -export interface NotificationCenter { - addNotificationListener<T extends ListenerPayload>( - notificationType: string, - callback: NotificationListener<T> - ): number; - removeNotificationListener(listenerId: number): boolean; - clearAllNotificationListeners(): void; - clearNotificationListeners(notificationType: NOTIFICATION_TYPES): void; -} - // An event to be submitted to Optimizely, enabling tracking the reach and impact of // tests and feature rollouts. export interface Event { @@ -295,7 +283,7 @@ export interface OptimizelyOptions { defaultDecideOptions?: OptimizelyDecideOption[]; isSsr?:boolean; odpManager?: IOdpManager; - notificationCenter: NotificationCenterImpl; + notificationCenter: DefaultNotificationCenter; } /** diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index db8575729..10a5deb3f 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -285,55 +285,7 @@ export const DECISION_MESSAGES = { VARIABLE_VALUE_INVALID: 'Variable value for key "%s" is invalid or wrong type.', }; -/* - * Notification types for use with NotificationCenter - * Format is EVENT: <list of parameters to callback> - * - * SDK consumers can use these to register callbacks with the notification center. - * - * @deprecated since 3.1.0 - * ACTIVATE: An impression event will be sent to Optimizely - * Callbacks will receive an object argument with the following properties: - * - experiment {Object} - * - userId {string} - * - attributes {Object|undefined} - * - variation {Object} - * - logEvent {Object} - * - * DECISION: A decision is made in the system. i.e. user activation, - * feature access or feature-variable value retrieval - * Callbacks will receive an object argument with the following properties: - * - type {string} - * - userId {string} - * - attributes {Object|undefined} - * - decisionInfo {Object|undefined} - * - * LOG_EVENT: A batch of events, which could contain impressions and/or conversions, - * will be sent to Optimizely - * Callbacks will receive an object argument with the following properties: - * - url {string} - * - httpVerb {string} - * - params {Object} - * - * OPTIMIZELY_CONFIG_UPDATE: This Optimizely instance has been updated with a new - * config - * - * TRACK: A conversion event will be sent to Optimizely - * Callbacks will receive the an object argument with the following properties: - * - eventKey {string} - * - userId {string} - * - attributes {Object|undefined} - * - eventTags {Object|undefined} - * - logEvent {Object} - * - */ -export enum NOTIFICATION_TYPES { - ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', - DECISION = 'DECISION:type, userId, attributes, decisionInfo', - LOG_EVENT = 'LOG_EVENT:logEvent', - OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE', - TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event', -} +export { NOTIFICATION_TYPES } from '../../notification_center/type'; /** * Default milliseconds before request timeout diff --git a/lib/utils/event_emitter/event_emitter.ts b/lib/utils/event_emitter/event_emitter.ts index 22b22be5d..6bfa57f8d 100644 --- a/lib/utils/event_emitter/event_emitter.ts +++ b/lib/utils/event_emitter/event_emitter.ts @@ -47,6 +47,10 @@ export class EventEmitter<T> { } } + removeListeners<E extends keyof T>(eventName: E): void { + this.listeners[eventName]?.clear(); + } + removeAllListeners(): void { this.listeners = {}; } From faee6c76ecc91bfae514571e02bf58bc1113a211 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 12 Dec 2024 00:50:53 +0600 Subject: [PATCH 104/200] [FSSDK-10993] additional cleanup for project config manager (#977) --- .../config_manager_factory.browser.spec.ts | 3 +- .../config_manager_factory.node.spec.ts | 4 +- ...onfig_manager_factory.react_native.spec.ts | 39 +++----- .../config_manager_factory.react_native.ts | 4 +- .../config_manager_factory.spec.ts | 34 ++++++- lib/project_config/config_manager_factory.ts | 19 +++- lib/project_config/datafile_manager.ts | 9 +- .../optimizely_config.tests.js} | 6 +- .../optimizely_config.ts} | 8 +- .../polling_datafile_manager.spec.ts | 92 ++++++++----------- .../polling_datafile_manager.ts | 40 ++++---- lib/project_config/project_config_manager.ts | 2 +- 12 files changed, 135 insertions(+), 125 deletions(-) rename lib/{core/optimizely_config/index.tests.js => project_config/optimizely_config.tests.js} (99%) rename lib/{core/optimizely_config/index.ts => project_config/optimizely_config.ts} (98%) diff --git a/lib/project_config/config_manager_factory.browser.spec.ts b/lib/project_config/config_manager_factory.browser.spec.ts index bbabfb0ac..7141cc16c 100644 --- a/lib/project_config/config_manager_factory.browser.spec.ts +++ b/lib/project_config/config_manager_factory.browser.spec.ts @@ -30,6 +30,7 @@ vi.mock('../utils/http_request_handler/browser_request_handler', () => { import { getPollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.browser'; import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); @@ -76,7 +77,7 @@ describe('createPollingConfigManager', () => { autoUpdate: true, urlTemplate: 'urlTemplate', datafileAccessToken: 'datafileAccessToken', - cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + cache: getMockSyncCache<string>(), }; const projectConfigManager = createPollingProjectConfigManager(config); diff --git a/lib/project_config/config_manager_factory.node.spec.ts b/lib/project_config/config_manager_factory.node.spec.ts index 2667e5cf5..6ef8e04e0 100644 --- a/lib/project_config/config_manager_factory.node.spec.ts +++ b/lib/project_config/config_manager_factory.node.spec.ts @@ -30,7 +30,7 @@ vi.mock('../utils/http_request_handler/node_request_handler', () => { import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.node'; import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; -import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE } from './constant'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); @@ -77,7 +77,7 @@ describe('createPollingConfigManager', () => { autoUpdate: false, urlTemplate: 'urlTemplate', datafileAccessToken: 'datafileAccessToken', - cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + cache: getMockSyncCache(), }; const projectConfigManager = createPollingProjectConfigManager(config); diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index 0ead808de..b047af03a 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -29,7 +29,7 @@ async function mockRequireAsyncStorage() { M._load_original = M._load; M._load = (uri: string, parent: string) => { if (uri === '@react-native-async-storage/async-storage') { - if (isAsyncStorageAvailable) return {}; + if (isAsyncStorageAvailable) return { default: {} }; throw new Error('Module not found: @react-native-async-storage/async-storage'); } return M._load_original(uri, parent); @@ -47,25 +47,30 @@ vi.mock('../utils/http_request_handler/browser_request_handler', () => { return { BrowserRequestHandler }; }); -vi.mock('../plugins/key_value_cache/reactNativeAsyncStorageCache', () => { - const ReactNativeAsyncStorageCache = vi.fn(); - return { default: ReactNativeAsyncStorageCache }; +vi.mock('../utils/cache/async_storage_cache.react_native', async (importOriginal) => { + const original: any = await importOriginal(); + const OriginalAsyncStorageCache = original.AsyncStorageCache; + const MockAsyncStorageCache = vi.fn().mockImplementation(function (this: any, ...args) { + Object.setPrototypeOf(this, new OriginalAsyncStorageCache(...args)); + }); + return { AsyncStorageCache: MockAsyncStorageCache }; }); import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.react_native'; import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; -import ReactNativeAsyncStorageCache from '../plugins/key_value_cache/reactNativeAsyncStorageCache'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); - const MockReactNativeAsyncStorageCache = vi.mocked(ReactNativeAsyncStorageCache); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); beforeEach(() => { mockGetPollingConfigManager.mockClear(); MockBrowserRequestHandler.mockClear(); - MockReactNativeAsyncStorageCache.mockClear(); + MockAsyncStorageCache.mockClear(); }); it('creates and returns the instance by calling getPollingConfigManager', () => { @@ -110,7 +115,7 @@ describe('createPollingConfigManager', () => { createPollingProjectConfigManager(config); expect( - Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockReactNativeAsyncStorageCache.mock.instances[0]) + Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockAsyncStorageCache.mock.instances[0]) ).toBe(true); }); @@ -123,7 +128,7 @@ describe('createPollingConfigManager', () => { autoUpdate: false, urlTemplate: 'urlTemplate', datafileAccessToken: 'datafileAccessToken', - cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + cache: getMockSyncCache(), }; createPollingProjectConfigManager(config); @@ -133,19 +138,12 @@ describe('createPollingConfigManager', () => { it('Should not throw error if a cache is present in the config, and async storage is not available', async () => { isAsyncStorageAvailable = false; - const { default: ReactNativeAsyncStorageCache } = await vi.importActual< - typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') - >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); const config = { sdkKey: 'sdkKey', requestHandler: { makeRequest: vi.fn() }, - cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + cache: getMockSyncCache<string>(), }; - MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { - return new ReactNativeAsyncStorageCache(); - }); - expect(() => createPollingProjectConfigManager(config)).not.toThrow(); isAsyncStorageAvailable = true; }); @@ -153,18 +151,11 @@ describe('createPollingConfigManager', () => { it('should throw an error if cache is not present in the config, and async storage is not available', async () => { isAsyncStorageAvailable = false; - const { default: ReactNativeAsyncStorageCache } = await vi.importActual< - typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') - >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); const config = { sdkKey: 'sdkKey', requestHandler: { makeRequest: vi.fn() }, }; - MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { - return new ReactNativeAsyncStorageCache(); - }); - expect(() => createPollingProjectConfigManager(config)).toThrowError( 'Module not found: @react-native-async-storage/async-storage' ); diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts index 984f3c9d0..17e71f045 100644 --- a/lib/project_config/config_manager_factory.react_native.ts +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -17,13 +17,13 @@ import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; import { ProjectConfigManager } from "./project_config_manager"; -import ReactNativeAsyncStorageCache from "../plugins/key_value_cache/reactNativeAsyncStorageCache"; +import { AsyncStorageCache } from "../utils/cache/async_storage_cache.react_native"; export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { const defaultConfig = { autoUpdate: true, requestHandler: new BrowserRequestHandler(), - cache: config.cache || new ReactNativeAsyncStorageCache() + cache: config.cache || new AsyncStorageCache(), }; return getPollingConfigManager({ ...defaultConfig, ...config }); diff --git a/lib/project_config/config_manager_factory.spec.ts b/lib/project_config/config_manager_factory.spec.ts index a79b3ae1a..e30cbf33e 100644 --- a/lib/project_config/config_manager_factory.spec.ts +++ b/lib/project_config/config_manager_factory.spec.ts @@ -36,7 +36,9 @@ import { ProjectConfigManagerImpl } from './project_config_manager'; import { PollingDatafileManager } from './polling_datafile_manager'; import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; import { getPollingConfigManager } from './config_manager_factory'; -import { DEFAULT_UPDATE_INTERVAL } from './constant'; +import { DEFAULT_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { LogLevel } from '../modules/logging'; describe('getPollingConfigManager', () => { const MockProjectConfigManagerImpl = vi.mocked(ProjectConfigManagerImpl); @@ -73,7 +75,32 @@ describe('getPollingConfigManager', () => { }; getPollingConfigManager(config); expect(MockIntervalRepeater.mock.calls[0][0]).toBe(DEFAULT_UPDATE_INTERVAL); - expect(MockPollingDatafileManager.mock.calls[0][0].updateInterval).toBe(DEFAULT_UPDATE_INTERVAL); + }); + + it('adds a startup log if the update interval is below the minimum', () => { + const config = { + sdkKey: 'abcd', + requestHandler: { makeRequest: vi.fn() }, + updateInterval: 10000, + }; + getPollingConfigManager(config); + const startupLogs = MockPollingDatafileManager.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.WARNING, + message: UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE, + params: [], + }])); + }); + + it('does not add any startup log if the update interval above the minimum', () => { + const config = { + sdkKey: 'abcd', + requestHandler: { makeRequest: vi.fn() }, + updateInterval: 40000, + }; + getPollingConfigManager(config); + const startupLogs = MockPollingDatafileManager.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual([]); }); it('uses the provided options', () => { @@ -86,7 +113,7 @@ describe('getPollingConfigManager', () => { autoUpdate: true, urlTemplate: 'urlTemplate', datafileAccessToken: 'datafileAccessToken', - cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + cache: getMockSyncCache<string>(), }; getPollingConfigManager(config); @@ -96,7 +123,6 @@ describe('getPollingConfigManager', () => { expect(MockPollingDatafileManager).toHaveBeenNthCalledWith(1, expect.objectContaining({ sdkKey: config.sdkKey, autoUpdate: config.autoUpdate, - updateInterval: config.updateInterval, urlTemplate: config.urlTemplate, datafileAccessToken: config.datafileAccessToken, requestHandler: config.requestHandler, diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 4d1977663..8cde539fa 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -19,9 +19,12 @@ import { Transformer } from "../utils/type"; import { DatafileManagerConfig } from "./datafile_manager"; import { ProjectConfigManagerImpl, ProjectConfigManager } from "./project_config_manager"; import { PollingDatafileManager } from "./polling_datafile_manager"; -import PersistentKeyValueCache from "../plugins/key_value_cache/persistentKeyValueCache"; +import { Cache } from "../utils/cache/cache"; import { DEFAULT_UPDATE_INTERVAL } from './constant'; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import { StartupLog } from "../service"; +import { MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; +import { LogLevel } from "../modules/logging"; export type StaticConfigManagerConfig = { datafile: string, @@ -42,7 +45,7 @@ export type PollingConfigManagerConfig = { updateInterval?: number; urlTemplate?: string; datafileAccessToken?: string; - cache?: PersistentKeyValueCache; + cache?: Cache<string>; }; export type PollingConfigManagerFactoryOptions = PollingConfigManagerConfig & { requestHandler: RequestHandler }; @@ -55,15 +58,25 @@ export const getPollingConfigManager = ( const backoff = new ExponentialBackoff(1000, updateInterval, 500); const repeater = new IntervalRepeater(updateInterval, backoff); + const startupLogs: StartupLog[] = [] + + if (updateInterval < MIN_UPDATE_INTERVAL) { + startupLogs.push({ + level: LogLevel.WARNING, + message: UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE, + params: [], + }); + } + const datafileManagerConfig: DatafileManagerConfig = { sdkKey: opt.sdkKey, autoUpdate: opt.autoUpdate, - updateInterval: updateInterval, urlTemplate: opt.urlTemplate, datafileAccessToken: opt.datafileAccessToken, requestHandler: opt.requestHandler, cache: opt.cache, repeater, + startupLogs, }; const datafileManager = new PollingDatafileManager(datafileManagerConfig); diff --git a/lib/project_config/datafile_manager.ts b/lib/project_config/datafile_manager.ts index 32798495e..3f38ea53c 100644 --- a/lib/project_config/datafile_manager.ts +++ b/lib/project_config/datafile_manager.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Service } from '../service'; -import PersistentKeyValueCache from '../plugins/key_value_cache/persistentKeyValueCache'; +import { Service, StartupLog } from '../service'; +import { Cache } from '../utils/cache/cache'; import { RequestHandler } from '../utils/http_request_handler/http'; import { Fn, Consumer } from '../utils/type'; import { Repeater } from '../utils/repeater/repeater'; @@ -30,12 +30,11 @@ export type DatafileManagerConfig = { requestHandler: RequestHandler; autoUpdate?: boolean; sdkKey: string; - /** Polling interval in milliseconds to check for datafile updates. */ - updateInterval?: number; urlTemplate?: string; - cache?: PersistentKeyValueCache; + cache?: Cache<string>; datafileAccessToken?: string; initRetry?: number; repeater: Repeater; logger?: LoggerFacade; + startupLogs?: StartupLog[]; } diff --git a/lib/core/optimizely_config/index.tests.js b/lib/project_config/optimizely_config.tests.js similarity index 99% rename from lib/core/optimizely_config/index.tests.js rename to lib/project_config/optimizely_config.tests.js index d4100e0da..22d2b95f3 100644 --- a/lib/core/optimizely_config/index.tests.js +++ b/lib/project_config/optimizely_config.tests.js @@ -17,15 +17,15 @@ import { assert } from 'chai'; import { cloneDeep } from 'lodash'; import sinon from 'sinon'; -import { createOptimizelyConfig, OptimizelyConfig } from './'; -import { createProjectConfig } from '../../project_config/project_config'; +import { createOptimizelyConfig, OptimizelyConfig } from './optimizely_config'; +import { createProjectConfig } from './project_config'; import { getTestProjectConfigWithFeatures, getTypedAudiencesConfig, getSimilarRuleKeyConfig, getSimilarExperimentKeyConfig, getDuplicateExperimentKeyConfig, -} from '../../tests/test_data'; +} from '../tests/test_data'; var datafile = getTestProjectConfigWithFeatures(); var typedAudienceDatafile = getTypedAudiencesConfig(); diff --git a/lib/core/optimizely_config/index.ts b/lib/project_config/optimizely_config.ts similarity index 98% rename from lib/core/optimizely_config/index.ts rename to lib/project_config/optimizely_config.ts index d8987b6c7..52eeb016c 100644 --- a/lib/core/optimizely_config/index.ts +++ b/lib/project_config/optimizely_config.ts @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerFacade, getLogger } from '../../modules/logging'; -import { ProjectConfig } from '../../project_config/project_config'; -import { DEFAULT_OPERATOR_TYPES } from '../condition_tree_evaluator'; +import { LoggerFacade, getLogger } from '../modules/logging'; +import { ProjectConfig } from '../project_config/project_config'; +import { DEFAULT_OPERATOR_TYPES } from '../core/condition_tree_evaluator'; import { Audience, Experiment, @@ -32,7 +32,7 @@ import { Rollout, Variation, VariationVariable, -} from '../../shared_types'; +} from '../shared_types'; interface FeatureVariablesMap { [key: string]: FeatureVariable[]; diff --git a/lib/project_config/polling_datafile_manager.spec.ts b/lib/project_config/polling_datafile_manager.spec.ts index 8e12ac3f5..3efae54d7 100644 --- a/lib/project_config/polling_datafile_manager.spec.ts +++ b/lib/project_config/polling_datafile_manager.spec.ts @@ -18,67 +18,45 @@ import { describe, it, expect, vi } from 'vitest'; import { PollingDatafileManager} from './polling_datafile_manager'; import { getMockRepeater } from '../tests/mock/mock_repeater'; import { getMockAbortableRequest, getMockRequestHandler } from '../tests/mock/mock_request_handler'; -import PersistentKeyValueCache from '../../lib/plugins/key_value_cache/persistentKeyValueCache'; import { getMockLogger } from '../tests/mock/mock_logger'; import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE, MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; -import { ServiceState } from '../service'; -import exp from 'constants'; - -const testCache = (): PersistentKeyValueCache => ({ - get(key: string): Promise<string | undefined> { - let val = undefined; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<boolean> { - return Promise.resolve(false); - }, -}); +import { ServiceState, StartupLog } from '../service'; +import { getMockSyncCache, getMockAsyncCache } from '../tests/mock/mock_cache'; +import { LogLevel } from '../modules/logging'; describe('PollingDatafileManager', () => { it('should log polling interval below MIN_UPDATE_INTERVAL', () => { const repeater = getMockRepeater(); const requestHandler = getMockRequestHandler(); const logger = getMockLogger(); - const manager = new PollingDatafileManager({ - repeater, - requestHandler, - sdkKey: '123', - logger, - updateInterval: MIN_UPDATE_INTERVAL - 1000, - }); - manager.start(); - expect(logger.warn).toHaveBeenCalledWith(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE); - }); - it('should not log polling interval above MIN_UPDATE_INTERVAL', () => { - const repeater = getMockRepeater(); - const requestHandler = getMockRequestHandler(); - const logger = getMockLogger(); + const startupLogs: StartupLog[] = [ + { + level: LogLevel.WARNING, + message: 'warn message', + params: [1, 2] + }, + { + level: LogLevel.ERROR, + message: 'error message', + params: [3, 4] + }, + ]; + const manager = new PollingDatafileManager({ repeater, requestHandler, sdkKey: '123', logger, - updateInterval: MIN_UPDATE_INTERVAL + 1000, + startupLogs, }); + manager.start(); - expect(logger.warn).not.toHaveBeenCalled(); + expect(logger.log).toHaveBeenNthCalledWith(1, LogLevel.WARNING, 'warn message', 1, 2); + expect(logger.log).toHaveBeenNthCalledWith(2, LogLevel.ERROR, 'error message', 3, 4); }); + it('starts the repeater with immediateExecution on start', () => { const repeater = getMockRepeater(); @@ -96,11 +74,14 @@ describe('PollingDatafileManager', () => { it('uses cached version of datafile, resolves onRunning() and calls onUpdate handlers while datafile fetch request waits', async () => { const repeater = getMockRepeater(); const requestHandler = getMockRequestHandler(); // response promise is pending + const cache = getMockAsyncCache<string>(); + await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' })); + const manager = new PollingDatafileManager({ repeater, requestHandler, sdkKey: 'keyThatExists', - cache: testCache(), + cache, }); manager.start(); @@ -117,12 +98,15 @@ describe('PollingDatafileManager', () => { const requestHandler = getMockRequestHandler(); const mockResponse = getMockAbortableRequest(Promise.reject('test error')); requestHandler.makeRequest.mockReturnValueOnce(mockResponse); - + + const cache = getMockAsyncCache<string>(); + await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' })); + const manager = new PollingDatafileManager({ repeater, requestHandler, sdkKey: 'keyThatExists', - cache: testCache(), + cache, }); manager.start(); @@ -139,14 +123,17 @@ describe('PollingDatafileManager', () => { it('uses cached version of datafile, then calls onUpdate when fetch request succeeds after the cache read', async () => { const repeater = getMockRepeater(); const requestHandler = getMockRequestHandler(); + const cache = getMockAsyncCache<string>(); + await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' })); const mockResponse = getMockAbortableRequest(); requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + const manager = new PollingDatafileManager({ repeater, requestHandler, sdkKey: 'keyThatExists', - cache: testCache(), + cache, }); manager.start(); @@ -170,10 +157,11 @@ describe('PollingDatafileManager', () => { const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); requestHandler.makeRequest.mockReturnValueOnce(mockResponse); - const cache = testCache(); + const cache = getMockAsyncCache<string>(); // this will be resolved after the requestHandler response is resolved const cachePromise = resolvablePromise<string | undefined>(); - cache.get = () => cachePromise.promise; + const getSpy = vi.spyOn(cache, 'get'); + getSpy.mockReturnValueOnce(cachePromise.promise); const manager = new PollingDatafileManager({ repeater, @@ -337,7 +325,7 @@ describe('PollingDatafileManager', () => { requestHandler, sdkKey: 'keyThatDoesNotExists', initRetry: 5, - cache: testCache(), + cache: getMockAsyncCache(), }); manager.start(); @@ -488,7 +476,7 @@ describe('PollingDatafileManager', () => { const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); requestHandler.makeRequest.mockReturnValueOnce(mockResponse); - const cache = testCache(); + const cache = getMockAsyncCache<string>(); const spy = vi.spyOn(cache, 'set'); const manager = new PollingDatafileManager({ @@ -551,7 +539,7 @@ describe('PollingDatafileManager', () => { requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); - const cache = testCache(); + const cache = getMockAsyncCache<string>(); const spy = vi.spyOn(cache, 'set'); const manager = new PollingDatafileManager({ diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index 585cb0949..f7223fc00 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -13,23 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { LoggerFacade } from '../modules/logging'; import { sprintf } from '../utils/fns'; import { DatafileManager, DatafileManagerConfig } from './datafile_manager'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; -import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE, MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; -import PersistentKeyValueCache from '../plugins/key_value_cache/persistentKeyValueCache'; - +import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE } from './constant'; +import { Cache } from '../utils/cache/cache'; import { BaseService, ServiceState } from '../service'; import { RequestHandler, AbortableRequest, Headers, Response } from '../utils/http_request_handler/http'; import { Repeater } from '../utils/repeater/repeater'; import { Consumer, Fn } from '../utils/type'; -import { url } from 'inspector'; - -function isSuccessStatusCode(statusCode: number): boolean { - return statusCode >= 200 && statusCode < 400; -} +import { isSuccessStatusCode } from '../utils/http_request_handler/http_util'; export class PollingDatafileManager extends BaseService implements DatafileManager { private requestHandler: RequestHandler; @@ -38,18 +31,16 @@ export class PollingDatafileManager extends BaseService implements DatafileManag private autoUpdate: boolean; private initRetryRemaining?: number; private repeater: Repeater; - private updateInterval?: number; - private lastResponseLastModified?: string; private datafileUrl: string; private currentRequest?: AbortableRequest; private cacheKey: string; - private cache?: PersistentKeyValueCache; + private cache?: Cache<string>; private sdkKey: string; private datafileAccessToken?: string; constructor(config: DatafileManagerConfig) { - super(); + super(config.startupLogs); const { autoUpdate = false, sdkKey, @@ -59,7 +50,6 @@ export class PollingDatafileManager extends BaseService implements DatafileManag initRetry, repeater, requestHandler, - updateInterval, logger, } = config; this.cache = cache; @@ -71,7 +61,6 @@ export class PollingDatafileManager extends BaseService implements DatafileManag this.autoUpdate = autoUpdate; this.initRetryRemaining = initRetry; this.repeater = repeater; - this.updateInterval = updateInterval; this.logger = logger; const urlTemplateToUse = urlTemplate || @@ -92,10 +81,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag return; } - if (this.updateInterval !== undefined && this.updateInterval < MIN_UPDATE_INTERVAL) { - this.logger?.warn(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE); - } - + super.start(); this.state = ServiceState.Starting; this.setDatafileFromCacheIfAvailable(); this.repeater.setTask(this.syncDatafile.bind(this)); @@ -230,11 +216,17 @@ export class PollingDatafileManager extends BaseService implements DatafileManag } } - private setDatafileFromCacheIfAvailable(): void { - this.cache?.get(this.cacheKey).then(datafile => { - if (datafile && this.isStarting()) { + private async setDatafileFromCacheIfAvailable(): Promise<void> { + if (!this.cache) { + return; + } + try { + const datafile = await this.cache.get(this.cacheKey); + if (datafile && this.isStarting()) { this.handleDatafile(datafile); } - }).catch(() => {}); + } catch { + // ignore error + } } } diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index 46c79238c..b71ef1f39 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { LoggerFacade } from '../modules/logging'; -import { createOptimizelyConfig } from '../core/optimizely_config'; +import { createOptimizelyConfig } from './optimizely_config'; import { OptimizelyConfig } from '../shared_types'; import { DatafileManager } from './datafile_manager'; import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from './project_config'; From e0aabf5eafa6d4a34be09a7d2e5d3aa4d1face1a Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 13 Dec 2024 01:49:13 +0600 Subject: [PATCH 105/200] [FSSDK-10950] refactor ODP implementation (#973) --- lib/export_types.ts | 1 - lib/index.browser.tests.js | 847 ++-------------- lib/index.browser.ts | 70 +- lib/index.node.ts | 44 +- lib/index.react_native.ts | 45 +- lib/odp/constant.ts | 28 + .../event_api_manager.browser.ts | 69 -- .../event_manager/event_api_manager.node.ts | 51 - .../event_manager/event_manager.browser.ts | 50 - lib/odp/event_manager/event_manager.node.ts | 50 - lib/odp/event_manager/odp_event.ts | 2 +- .../odp_event_api_manager.spec.ts | 206 ++++ .../event_manager/odp_event_api_manager.ts | 149 ++- .../event_manager/odp_event_manager.spec.ts | 940 ++++++++++++++++++ lib/odp/event_manager/odp_event_manager.ts | 502 +++------- lib/odp/odp_manager.browser.ts | 202 ---- lib/odp/odp_manager.node.ts | 144 --- lib/odp/odp_manager.spec.ts | 699 +++++++++++++ lib/odp/odp_manager.ts | 375 +++---- lib/odp/odp_manager_factory.browser.spec.ts | 112 +++ lib/odp/odp_manager_factory.browser.ts | 40 + lib/odp/odp_manager_factory.node.spec.ts | 125 +++ lib/odp/odp_manager_factory.node.ts | 43 + .../odp_manager_factory.react_native.spec.ts | 125 +++ lib/odp/odp_manager_factory.react_native.ts | 43 + lib/odp/odp_manager_factory.spec.ts | 405 ++++++++ lib/odp/odp_manager_factory.ts | 95 ++ lib/odp/odp_types.ts | 2 +- .../odp_response_schema.ts | 2 +- .../odp_segment_api_manager.spec.ts | 245 +++++ .../odp_segment_api_manager.ts | 55 +- .../odp_segment_manager.spec.ts | 180 ++++ .../segment_manager/odp_segment_manager.ts | 130 +-- .../optimizely_segment_option.ts | 2 +- lib/odp/ua_parser/ua_parser.browser.ts | 6 +- lib/odp/ua_parser/user_agent_parser.ts | 2 +- lib/optimizely/index.spec.ts | 18 +- lib/optimizely/index.ts | 74 +- .../browserAsyncStorageCache.ts | 75 -- lib/plugins/vuid_manager/index.ts | 141 --- lib/shared_types.ts | 48 +- lib/tests/mock/mock_repeater.ts | 7 +- lib/tests/testUtils.ts | 23 + lib/utils/cache/in_memory_lru_cache.spec.ts | 124 +++ lib/utils/cache/in_memory_lru_cache.ts | 78 ++ lib/utils/enums/index.ts | 22 - lib/utils/fns/index.ts | 1 + lib/utils/lru_cache/browser_lru_cache.ts | 36 - lib/utils/lru_cache/cache_element.tests.ts | 53 - lib/utils/lru_cache/cache_element.ts | 42 - lib/utils/lru_cache/lru_cache.tests.ts | 309 ------ lib/utils/lru_cache/lru_cache.ts | 132 --- lib/utils/lru_cache/server_lru_cache.ts | 36 - lib/utils/repeater/repeater.ts | 13 +- lib/vuid/vuid.spec.ts | 39 + lib/vuid/vuid.ts | 31 + lib/vuid/vuid_manager.spec.ts | 230 +++++ lib/vuid/vuid_manager.ts | 132 +++ lib/vuid/vuid_manager_factory.browser.spec.ts | 84 ++ lib/vuid/vuid_manager_factory.browser.ts | 28 + .../vuid_manager_factory.node.spec.ts} | 26 +- lib/vuid/vuid_manager_factory.node.ts | 22 + .../vuid_manager_factory.react_native.spec.ts | 85 ++ lib/vuid/vuid_manager_factory.react_native.ts | 28 + .../index.ts => vuid/vuid_manager_factory.ts} | 13 +- tests/browserAsyncStorageCache.spec.ts | 92 -- tests/odpEventApiManager.spec.ts | 139 --- tests/odpEventManager.spec.ts | 733 -------------- tests/odpManager.browser.spec.ts | 513 ---------- tests/odpManager.spec.ts | 698 ------------- tests/odpSegmentApiManager.spec.ts | 300 ------ tests/odpSegmentManager.spec.ts | 179 ---- tests/vuidManager.spec.ts | 102 -- 73 files changed, 4800 insertions(+), 5992 deletions(-) create mode 100644 lib/odp/constant.ts delete mode 100644 lib/odp/event_manager/event_api_manager.browser.ts delete mode 100644 lib/odp/event_manager/event_api_manager.node.ts delete mode 100644 lib/odp/event_manager/event_manager.browser.ts delete mode 100644 lib/odp/event_manager/event_manager.node.ts create mode 100644 lib/odp/event_manager/odp_event_api_manager.spec.ts create mode 100644 lib/odp/event_manager/odp_event_manager.spec.ts delete mode 100644 lib/odp/odp_manager.browser.ts delete mode 100644 lib/odp/odp_manager.node.ts create mode 100644 lib/odp/odp_manager.spec.ts create mode 100644 lib/odp/odp_manager_factory.browser.spec.ts create mode 100644 lib/odp/odp_manager_factory.browser.ts create mode 100644 lib/odp/odp_manager_factory.node.spec.ts create mode 100644 lib/odp/odp_manager_factory.node.ts create mode 100644 lib/odp/odp_manager_factory.react_native.spec.ts create mode 100644 lib/odp/odp_manager_factory.react_native.ts create mode 100644 lib/odp/odp_manager_factory.spec.ts create mode 100644 lib/odp/odp_manager_factory.ts rename lib/odp/{ => segment_manager}/odp_response_schema.ts (99%) create mode 100644 lib/odp/segment_manager/odp_segment_api_manager.spec.ts create mode 100644 lib/odp/segment_manager/odp_segment_manager.spec.ts delete mode 100644 lib/plugins/key_value_cache/browserAsyncStorageCache.ts delete mode 100644 lib/plugins/vuid_manager/index.ts create mode 100644 lib/tests/testUtils.ts create mode 100644 lib/utils/cache/in_memory_lru_cache.spec.ts create mode 100644 lib/utils/cache/in_memory_lru_cache.ts delete mode 100644 lib/utils/lru_cache/browser_lru_cache.ts delete mode 100644 lib/utils/lru_cache/cache_element.tests.ts delete mode 100644 lib/utils/lru_cache/cache_element.ts delete mode 100644 lib/utils/lru_cache/lru_cache.tests.ts delete mode 100644 lib/utils/lru_cache/lru_cache.ts delete mode 100644 lib/utils/lru_cache/server_lru_cache.ts create mode 100644 lib/vuid/vuid.spec.ts create mode 100644 lib/vuid/vuid.ts create mode 100644 lib/vuid/vuid_manager.spec.ts create mode 100644 lib/vuid/vuid_manager.ts create mode 100644 lib/vuid/vuid_manager_factory.browser.spec.ts create mode 100644 lib/vuid/vuid_manager_factory.browser.ts rename lib/{odp/odp_utils.ts => vuid/vuid_manager_factory.node.spec.ts} (51%) create mode 100644 lib/vuid/vuid_manager_factory.node.ts create mode 100644 lib/vuid/vuid_manager_factory.react_native.spec.ts create mode 100644 lib/vuid/vuid_manager_factory.react_native.ts rename lib/{utils/lru_cache/index.ts => vuid/vuid_manager_factory.ts} (63%) delete mode 100644 tests/browserAsyncStorageCache.spec.ts delete mode 100644 tests/odpEventApiManager.spec.ts delete mode 100644 tests/odpEventManager.spec.ts delete mode 100644 tests/odpManager.browser.spec.ts delete mode 100644 tests/odpManager.spec.ts delete mode 100644 tests/odpSegmentApiManager.spec.ts delete mode 100644 tests/odpSegmentManager.spec.ts delete mode 100644 tests/vuidManager.spec.ts diff --git a/lib/export_types.ts b/lib/export_types.ts index df11a89a8..a55f56f27 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -45,5 +45,4 @@ export { TrackListenerPayload, NotificationCenter, OptimizelySegmentOption, - ICache, } from './shared_types'; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 15145c7a6..0a7859353 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -23,14 +23,6 @@ import testData from './tests/test_data'; import packageJSON from '../package.json'; import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; -import OptimizelyUserContext from './optimizely_user_context'; - -import { LOG_MESSAGES, ODP_EVENT_ACTION } from './utils/enums'; -import { BrowserOdpManager } from './odp/odp_manager.browser'; -import { OdpConfig } from './odp/odp_config'; -import { BrowserOdpEventManager } from './odp/event_manager/event_manager.browser'; -import { BrowserOdpEventApiManager } from './odp/event_manager/event_api_manager.browser'; -import { OdpEvent } from './odp/event_manager/odp_event'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; @@ -432,152 +424,6 @@ describe('javascript-sdk (Browser)', function() { sinon.assert.calledWithExactly(logging.setLogHandler, fakeLogger); }); }); - - // TODO: user will create and inject an event processor - // these tests will be refactored accordingly - // describe('event processor configuration', function() { - // beforeEach(function() { - // sinon.stub(eventProcessor, 'createEventProcessor'); - // }); - - // afterEach(function() { - // eventProcessor.createEventProcessor.restore(); - // }); - - // it('should use default event flush interval when none is provided', function() { - // optimizelyFactory.createInstance({ - // datafile: testData.getTestProjectConfigWithFeatures(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // logger: silentLogger, - // }); - // sinon.assert.calledWithExactly( - // eventProcessor.createEventProcessor, - // sinon.match({ - // flushInterval: 1000, - // }) - // ); - // }); - - // describe('with an invalid flush interval', function() { - // beforeEach(function() { - // sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(false); - // }); - - // afterEach(function() { - // eventProcessorConfigValidator.validateEventFlushInterval.restore(); - // }); - - // it('should ignore the event flush interval and use the default instead', function() { - // optimizelyFactory.createInstance({ - // datafile: testData.getTestProjectConfigWithFeatures(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // logger: silentLogger, - // eventFlushInterval: ['invalid', 'flush', 'interval'], - // }); - // sinon.assert.calledWithExactly( - // eventProcessor.createEventProcessor, - // sinon.match({ - // flushInterval: 1000, - // }) - // ); - // }); - // }); - - // describe('with a valid flush interval', function() { - // beforeEach(function() { - // sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(true); - // }); - - // afterEach(function() { - // eventProcessorConfigValidator.validateEventFlushInterval.restore(); - // }); - - // it('should use the provided event flush interval', function() { - // optimizelyFactory.createInstance({ - // datafile: testData.getTestProjectConfigWithFeatures(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // logger: silentLogger, - // eventFlushInterval: 9000, - // }); - // sinon.assert.calledWithExactly( - // eventProcessor.createEventProcessor, - // sinon.match({ - // flushInterval: 9000, - // }) - // ); - // }); - // }); - - // it('should use default event batch size when none is provided', function() { - // optimizelyFactory.createInstance({ - // datafile: testData.getTestProjectConfigWithFeatures(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // logger: silentLogger, - // }); - // sinon.assert.calledWithExactly( - // eventProcessor.createEventProcessor, - // sinon.match({ - // batchSize: 10, - // }) - // ); - // }); - - // describe('with an invalid event batch size', function() { - // beforeEach(function() { - // sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(false); - // }); - - // afterEach(function() { - // eventProcessorConfigValidator.validateEventBatchSize.restore(); - // }); - - // it('should ignore the event batch size and use the default instead', function() { - // optimizelyFactory.createInstance({ - // datafile: testData.getTestProjectConfigWithFeatures(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // logger: silentLogger, - // eventBatchSize: null, - // }); - // sinon.assert.calledWithExactly( - // eventProcessor.createEventProcessor, - // sinon.match({ - // batchSize: 10, - // }) - // ); - // }); - // }); - - // describe('with a valid event batch size', function() { - // beforeEach(function() { - // sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(true); - // }); - - // afterEach(function() { - // eventProcessorConfigValidator.validateEventBatchSize.restore(); - // }); - - // it('should use the provided event batch size', function() { - // optimizelyFactory.createInstance({ - // datafile: testData.getTestProjectConfigWithFeatures(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // logger: silentLogger, - // eventBatchSize: 300, - // }); - // sinon.assert.calledWithExactly( - // eventProcessor.createEventProcessor, - // sinon.match({ - // batchSize: 300, - // }) - // ); - // }); - // }); - // }); }); describe('ODP/ATS', () => { @@ -624,627 +470,118 @@ describe('javascript-sdk (Browser)', function() { requestParams.clear(); }); - it('should send identify event by default when initialized', async () => { - new OptimizelyUserContext({ - optimizely: fakeOptimizely, - userId: testFsUserId, - }); - - await fakeOptimizely.onReady(); - - sinon.assert.calledOnce(fakeOptimizely.identifyUser); - - sinon.assert.calledWith(fakeOptimizely.identifyUser, testFsUserId); - }); - - it('should log info when odp is disabled', () => { - const disabledClient = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { disabled: true }, - odpManager: BrowserOdpManager.createInstance({ - logger, - odpOptions: { - disabled: true, - }, - }), - }); - - sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, LOG_MESSAGES.ODP_DISABLED); - }); - - it('should include the VUID instantation promise of Browser ODP Manager in the Optimizely client onReady promise dependency array', () => { - const client = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - onRunning: Promise.resolve(), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpManager: BrowserOdpManager.createInstance({ - logger, - }), - }); - - client - .onReady() - .then(() => { - assert.isDefined(client.odpManager.initPromise); - client.odpManager.initPromise - .then(() => { - assert.isTrue(true); - }) - .catch(() => { - assert.isTrue(false); - }); - assert.isDefined(client.odpManager.getVuid()); - }) - .catch(() => { - assert.isTrue(false); - }); - - sinon.assert.neverCalledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.ERROR); - }); - - it('should accept a valid custom cache size', () => { - const client = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - onRunning: Promise.resolve(), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpManager: BrowserOdpManager.createInstance({ - logger, - odpOptions: { - segmentsCacheSize: 10, - }, - }), - }); - - sinon.assert.calledWith( - logger.log, - optimizelyFactory.enums.LOG_LEVEL.DEBUG, - 'Provisioning cache with maxSize of 10' - ); - }); - - it('should accept a custom cache timeout', () => { - const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpManager: BrowserOdpManager.createInstance({ - logger, - odpOptions: { - segmentsCacheTimeout: 10, - }, - }), - }); - - sinon.assert.calledWith( - logger.log, - optimizelyFactory.enums.LOG_LEVEL.DEBUG, - 'Provisioning cache with timeout of 10' - ); - }); - - it('should accept both a custom cache size and timeout', () => { - const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - segmentsCacheSize: 10, - segmentsCacheTimeout: 10, - }, - }); - - sinon.assert.calledWith( - logger.log, - optimizelyFactory.enums.LOG_LEVEL.DEBUG, - 'Provisioning cache with maxSize of 10' - ); - - sinon.assert.calledWith( - logger.log, - optimizelyFactory.enums.LOG_LEVEL.DEBUG, - 'Provisioning cache with timeout of 10' - ); - }); - - it('should accept a valid custom odp segment manager', async () => { - const fakeSegmentManager = { - fetchQualifiedSegments: sinon.stub().returns(['a']), - updateSettings: sinon.spy(), - }; - - const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - segmentManager: fakeSegmentManager, - }, - }); - - projectConfigManager.pushUpdate(config); - - const readyData = await client.onReady(); - - sinon.assert.called(fakeSegmentManager.updateSettings); - - const segments = await client.fetchQualifiedSegments(testVuid); - assert.deepEqual(segments, ['a']); - - sinon.assert.notCalled(logger.error); - sinon.assert.called(fakeSegmentManager.fetchQualifiedSegments); - }); - - it('should accept a valid custom odp event manager', async () => { - const fakeEventManager = { - start: sinon.spy(), - updateSettings: sinon.spy(), - flush: sinon.spy(), - stop: sinon.spy(), - registerVuid: sinon.spy(), - identifyUser: sinon.spy(), - sendEvent: sinon.spy(), - }; - - const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - disabled: false, - eventManager: fakeEventManager, - }, - }); - projectConfigManager.pushUpdate(config); - - await client.onReady(); - - sinon.assert.called(fakeEventManager.start); - }); - - it('should send an odp event when calling sendOdpEvent with valid parameters', async () => { - const fakeEventManager = { - updateSettings: sinon.spy(), - start: sinon.spy(), - stop: sinon.spy(), - registerVuid: sinon.spy(), - identifyUser: sinon.spy(), - sendEvent: sinon.spy(), - flush: sinon.spy(), - }; - - const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - eventManager: fakeEventManager, - }, - }); - - projectConfigManager.pushUpdate(config); - await client.onReady(); - - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); - - sinon.assert.notCalled(logger.error); - sinon.assert.called(fakeEventManager.sendEvent); - }); - - it('should augment odp events with user agent data if userAgentParser is provided', async () => { - const userAgentParser = { - parseUserAgentInfo() { - return { - os: { name: 'windows', version: '11' }, - device: { type: 'laptop', model: 'thinkpad' }, - }; - }, - }; - - const fakeRequestHandler = { - makeRequest: sinon.spy(function(requestUrl, headers, method, data) { - return { - abort: () => {}, - responsePromise: Promise.resolve({ statusCode: 200 }), - }; - }), - }; - - const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - userAgentParser, - eventRequestHandler: fakeRequestHandler, - }, - }); - projectConfigManager.pushUpdate(config); - await client.onReady(); - - client.sendOdpEvent('test', '', new Map([['eamil', 'test@test.test']]), new Map([['key', 'value']])); - clock.tick(10000); - - const eventRequestUrl = new URL(fakeRequestHandler.makeRequest.lastCall.args[0]); - const searchParams = eventRequestUrl.searchParams; - - assert.equal(searchParams.get('os'), 'windows'); - assert.equal(searchParams.get('os_version'), '11'); - assert.equal(searchParams.get('device_type'), 'laptop'); - assert.equal(searchParams.get('model'), 'thinkpad'); - }); - - it('should convert fs-user-id, FS-USER-ID, and FS_USER_ID to fs_user_id identifier when calling sendOdpEvent', async () => { - const fakeEventManager = { - updateSettings: sinon.spy(), - start: sinon.spy(), - stop: sinon.spy(), - registerVuid: sinon.spy(), - identifyUser: sinon.spy(), - sendEvent: sinon.spy(), - flush: sinon.spy(), - }; - const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - eventManager: fakeEventManager, - }, - }); - projectConfigManager.pushUpdate(config); - await client.onReady(); - - // fs-user-id - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['fs-user-id', 'fsUserA']])); - sinon.assert.notCalled(logger.error); - sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); - - const sendEventArgs1 = fakeEventManager.sendEvent.args; - assert.deepEqual( - sendEventArgs1[0].toString(), - new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() - ); - - // FS-USER-ID - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['FS-USER-ID', 'fsUserA']])); - sinon.assert.notCalled(logger.error); - sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); - - const sendEventArgs2 = fakeEventManager.sendEvent.args; - assert.deepEqual( - sendEventArgs2[0].toString(), - new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() - ); - - // FS_USER_ID - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['FS_USER_ID', 'fsUserA']])); - sinon.assert.notCalled(logger.error); - sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); - - const sendEventArgs3 = fakeEventManager.sendEvent.args; - assert.deepEqual( - sendEventArgs3[0].toString(), - new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() - ); - - // fs_user_id - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED, undefined, new Map([['fs_user_id', 'fsUserA']])); - sinon.assert.notCalled(logger.error); - sinon.assert.neverCalledWith(logger.warn, LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); - - const sendEventArgs4 = fakeEventManager.sendEvent.args; - assert.deepEqual( - sendEventArgs4[0].toString(), - new OdpEvent('fullstack', 'client_initialized', new Map([['fs_user_id', 'fsUserA']]), new Map()).toString() - ); - }); - - it('should throw an error and not send an odp event when calling sendOdpEvent with an invalid action input', async () => { - const fakeEventManager = { - updateSettings: sinon.spy(), - start: sinon.spy(), - stop: sinon.spy(), - registerVuid: sinon.spy(), - identifyUser: sinon.spy(), - sendEvent: sinon.spy(), - flush: sinon.spy(), - }; - - const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - eventManager: fakeEventManager, - }, - }); - - projectConfigManager.pushUpdate(config); - await client.onReady(); - - client.sendOdpEvent(''); - sinon.assert.called(logger.error); - - client.sendOdpEvent(null); - sinon.assert.calledTwice(logger.error); - - client.sendOdpEvent(undefined); - sinon.assert.calledThrice(logger.error); - - sinon.assert.notCalled(fakeEventManager.sendEvent); - }); - - it('should use fullstack as a fallback value for the odp event when calling sendOdpEvent with an empty type input', async () => { - const fakeEventManager = { - updateSettings: sinon.spy(), - start: sinon.spy(), - stop: sinon.spy(), - registerVuid: sinon.spy(), - identifyUser: sinon.spy(), - sendEvent: sinon.spy(), - flush: sinon.spy(), - }; - - const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); + // TODO: these tests should be elsewhere + // it('should send an odp event when calling sendOdpEvent with valid parameters', async () => { + // const fakeEventManager = { + // updateSettings: sinon.spy(), + // start: sinon.spy(), + // stop: sinon.spy(), + // registerVuid: sinon.spy(), + // identifyUser: sinon.spy(), + // sendEvent: sinon.spy(), + // flush: sinon.spy(), + // }; - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - eventManager: fakeEventManager, - }, - }); - projectConfigManager.pushUpdate(config); - await client.onReady(); - - client.sendOdpEvent('dummy-action', ''); - - const sendEventArgs = fakeEventManager.sendEvent.args; - - const expectedEventArgs = new OdpEvent('fullstack', 'dummy-action', new Map(), new Map()); - assert.deepEqual(JSON.stringify(sendEventArgs[0][0]), JSON.stringify(expectedEventArgs)); - }); - - it('should log an error when attempting to send an odp event when odp is disabled', async () => { - const config = createProjectConfig(testData.getTestProjectConfigWithFeatures()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - disabled: true, - }, - }); - - projectConfigManager.pushUpdate(config); - - await client.onReady(); - - assert.isUndefined(client.odpManager); - sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); - - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); - - sinon.assert.calledWith( - logger.error, - optimizelyFactory.enums.ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING - ); - }); - - it('should log a warning when attempting to use an event batch size other than 1', async () => { - const config = createProjectConfig(testData.getTestProjectConfigWithFeatures()); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); - - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - eventBatchSize: 5, - }, - }); - - projectConfigManager.pushUpdate(config); - - await client.onReady(); - - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); + // const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); + // const projectConfigManager = getMockProjectConfigManager({ + // initConfig: config, + // onRunning: Promise.resolve(), + // }); - sinon.assert.calledWith( - logger.log, - optimizelyFactory.enums.LOG_LEVEL.WARNING, - 'ODP event batch size must be 1 in the browser.' - ); - assert(client.odpManager.eventManager.batchSize, 1); - }); + // const client = optimizelyFactory.createInstance({ + // projectConfigManager, + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // eventBatchSize: null, + // logger, + // odpOptions: { + // eventManager: fakeEventManager, + // }, + // }); - it('should send an odp event to the browser endpoint', async () => { - const odpConfig = new OdpConfig(); + // projectConfigManager.pushUpdate(config); + // await client.onReady(); - const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); - const eventManager = new BrowserOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine: 'javascript-sdk', - clientVersion: 'great', - }); + // client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); - let datafile = testData.getOdpIntegratedConfigWithSegments(); - const config = createProjectConfig(datafile); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); + // sinon.assert.notCalled(logger.error); + // sinon.assert.called(fakeEventManager.sendEvent); + // }); - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - odpConfig, - eventManager, - }, - }); - projectConfigManager.pushUpdate(config); - await client.onReady(); + // it('should log an error when attempting to send an odp event when odp is disabled', async () => { + // const config = createProjectConfig(testData.getTestProjectConfigWithFeatures()); + // const projectConfigManager = getMockProjectConfigManager({ + // initConfig: config, + // onRunning: Promise.resolve(), + // }); - client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); + // const client = optimizelyFactory.createInstance({ + // projectConfigManager, + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // eventBatchSize: null, + // logger, + // odpOptions: { + // disabled: true, + // }, + // }); - // wait for request to be sent - clock.tick(100); + // projectConfigManager.pushUpdate(config); - let publicKey = datafile.integrations[0].publicKey; - let pixelUrl = datafile.integrations[0].pixelUrl; + // await client.onReady(); - const pixelApiEndpoint = `${pixelUrl}/v2/zaius.gif`; - let requestEndpoint = new URL(requestParams.get('endpoint')); - assert.equal(requestEndpoint.origin + requestEndpoint.pathname, pixelApiEndpoint); - assert.equal(requestParams.get('method'), 'GET'); + // assert.isUndefined(client.odpManager); + // sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); - let searchParams = requestEndpoint.searchParams; - assert.lengthOf(searchParams.get('idempotence_id'), 36); - assert.equal(searchParams.get('data_source'), 'javascript-sdk'); - assert.equal(searchParams.get('data_source_type'), 'sdk'); - assert.equal(searchParams.get('data_source_version'), 'great'); - assert.equal(searchParams.get('tracker_id'), publicKey); - assert.equal(searchParams.get('event_type'), 'fullstack'); - assert.equal(searchParams.get('vdl_action'), ODP_EVENT_ACTION.INITIALIZED); - assert.isTrue(searchParams.get('vuid').startsWith('vuid_')); - assert.isNotNull(searchParams.get('data_source_version')); + // client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); - sinon.assert.notCalled(logger.error); - }); + // sinon.assert.calledWith( + // logger.error, + // optimizelyFactory.enums.ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING + // ); + // }); - it('should send odp client_initialized on client instantiation', async () => { - const odpConfig = new OdpConfig('key', 'host', 'pixel', []); - const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); - sinon.spy(apiManager, 'sendEvents'); - const eventManager = new BrowserOdpEventManager({ - odpConfig, - apiManager, - logger, - }); - const datafile = testData.getOdpIntegratedConfigWithSegments(); - const config = createProjectConfig(datafile); - const projectConfigManager = getMockProjectConfigManager({ - initConfig: config, - onRunning: Promise.resolve(), - }); + // it('should send odp client_initialized on client instantiation', async () => { + // const odpConfig = new OdpConfig('key', 'host', 'pixel', []); + // const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); + // sinon.spy(apiManager, 'sendEvents'); + // const eventManager = new BrowserOdpEventManager({ + // odpConfig, + // apiManager, + // logger, + // }); + // const datafile = testData.getOdpIntegratedConfigWithSegments(); + // const config = createProjectConfig(datafile); + // const projectConfigManager = getMockProjectConfigManager({ + // initConfig: config, + // onRunning: Promise.resolve(), + // }); - const client = optimizelyFactory.createInstance({ - projectConfigManager, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - eventBatchSize: null, - logger, - odpOptions: { - odpConfig, - eventManager, - }, - }); + // const client = optimizelyFactory.createInstance({ + // projectConfigManager, + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // eventBatchSize: null, + // logger, + // odpOptions: { + // odpConfig, + // eventManager, + // }, + // }); - projectConfigManager.pushUpdate(config); - await client.onReady(); + // projectConfigManager.pushUpdate(config); + // await client.onReady(); - clock.tick(100); + // clock.tick(100); - const [_, events] = apiManager.sendEvents.getCall(0).args; + // const [_, events] = apiManager.sendEvents.getCall(0).args; - const [firstEvent] = events; - assert.equal(firstEvent.action, 'client_initialized'); - assert.equal(firstEvent.type, 'fullstack'); - }); + // const [firstEvent] = events; + // assert.equal(firstEvent.action, 'client_initialized'); + // assert.equal(firstEvent.type, 'fullstack'); + // }); }); }); }); diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 05cc88075..7317540db 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -24,14 +24,16 @@ import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; -import { BrowserOdpManager } from './odp/odp_manager.browser'; import Optimizely from './optimizely'; -import { IUserAgentParser } from './odp/ua_parser/user_agent_parser'; +import { UserAgentParser } from './odp/ua_parser/user_agent_parser'; import { getUserAgentParser } from './odp/ua_parser/ua_parser.browser'; import * as commonExports from './common_exports'; import { PollingConfigManagerConfig } from './project_config/config_manager_factory'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.browser'; +import { createVuidManager } from './vuid/vuid_manager_factory.browser'; +import { createOdpManager } from './odp/odp_manager_factory.browser'; + const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -75,73 +77,19 @@ const createInstance = function(config: Config): Client | null { logger.error(ex); } - // let eventDispatcher; - // // prettier-ignore - // if (config.eventDispatcher == null) { // eslint-disable-line eqeqeq - // // only wrap the event dispatcher with pending events retry if the user didnt override - // eventDispatcher = new LocalStoragePendingEventsDispatcher({ - // eventDispatcher: defaultEventDispatcher, - // }); - - // if (!hasRetriedEvents) { - // eventDispatcher.sendPendingEvents(); - // hasRetriedEvents = true; - // } - // } else { - // eventDispatcher = config.eventDispatcher; - // } - - // let closingDispatcher = config.closingEventDispatcher; - - // if (!config.eventDispatcher && !closingDispatcher && window.navigator && 'sendBeacon' in window.navigator) { - // closingDispatcher = sendBeaconEventDispatcher; - // } - - // let eventBatchSize = config.eventBatchSize; - // let eventFlushInterval = config.eventFlushInterval; - - // if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - // logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - // eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - // } - // if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - // logger.warn( - // 'Invalid eventFlushInterval %s, defaulting to %s', - // config.eventFlushInterval, - // DEFAULT_EVENT_FLUSH_INTERVAL - // ); - // eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - // } - const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - // const eventProcessorConfig = { - // dispatcher: eventDispatcher, - // closingDispatcher, - // flushInterval: eventFlushInterval, - // batchSize: eventBatchSize, - // maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - // notificationCenter, - // }; - - const odpExplicitlyOff = config.odpOptions?.disabled === true; - if (odpExplicitlyOff) { - logger.info(enums.LOG_MESSAGES.ODP_DISABLED); - } - const { clientEngine, clientVersion } = config; const optimizelyOptions: OptimizelyOptions = { - clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, - // eventProcessor: eventProcessor.createEventProcessor(eventProcessorConfig), + clientEngine: clientEngine || enums.JAVASCRIPT_CLIENT_ENGINE, + clientVersion: clientVersion || enums.CLIENT_VERSION, logger, errorHandler, notificationCenter, isValidInstance, - odpManager: odpExplicitlyOff ? undefined - : BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; const optimizely = new Optimizely(optimizelyOptions); @@ -192,11 +140,13 @@ export { createInstance, __internalResetRetryState, OptimizelyDecideOption, - IUserAgentParser, + UserAgentParser as IUserAgentParser, getUserAgentParser, createPollingProjectConfigManager, createForwardingEventProcessor, createBatchEventProcessor, + createOdpManager, + createVuidManager, }; export * from './common_exports'; @@ -217,6 +167,8 @@ export default { createPollingProjectConfigManager, createForwardingEventProcessor, createBatchEventProcessor, + createOdpManager, + createVuidManager, }; export * from './export_types'; diff --git a/lib/index.node.ts b/lib/index.node.ts index a5a3b2968..63f7e16e5 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -23,10 +23,11 @@ import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { NodeOdpManager } from './odp/odp_manager.node'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; +import { createVuidManager } from './vuid/vuid_manager_factory.node'; +import { createOdpManager } from './odp/odp_manager_factory.node'; const logger = getLogger(); setLogLevel(LogLevel.ERROR); @@ -72,53 +73,20 @@ const createInstance = function(config: Config): Client | null { } } - // let eventBatchSize = config.eventBatchSize; - // let eventFlushInterval = config.eventFlushInterval; - - // if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - // logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - // eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - // } - // if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - // logger.warn( - // 'Invalid eventFlushInterval %s, defaulting to %s', - // config.eventFlushInterval, - // DEFAULT_EVENT_FLUSH_INTERVAL - // ); - // eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - // } const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - // const eventProcessorConfig = { - // dispatcher: config.eventDispatcher || defaultEventDispatcher, - // flushInterval: eventFlushInterval, - // batchSize: eventBatchSize, - // maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - // notificationCenter, - // }; - - // const eventProcessor = createEventProcessor(eventProcessorConfig); - // const eventProcessor = config.eventProcessor; - - const odpExplicitlyOff = config.odpOptions?.disabled === true; - if (odpExplicitlyOff) { - logger.info(enums.LOG_MESSAGES.ODP_DISABLED); - } - const { clientEngine, clientVersion } = config; const optimizelyOptions = { - clientEngine: enums.NODE_CLIENT_ENGINE, ...config, - // eventProcessor, + clientEngine: clientEngine || enums.NODE_CLIENT_ENGINE, + clientVersion: clientVersion || enums.CLIENT_VERSION, logger, errorHandler, notificationCenter, isValidInstance, - odpManager: odpExplicitlyOff ? undefined - : NodeOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; return new Optimizely(optimizelyOptions); @@ -144,6 +112,8 @@ export { createPollingProjectConfigManager, createForwardingEventProcessor, createBatchEventProcessor, + createOdpManager, + createVuidManager, }; export * from './common_exports'; @@ -161,6 +131,8 @@ export default { createPollingProjectConfigManager, createForwardingEventProcessor, createBatchEventProcessor, + createOdpManager, + createVuidManager, }; export * from './export_types'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index c0417d588..8cedf06d5 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -23,10 +23,11 @@ import * as loggerPlugin from './plugins/logger/index.react_native'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { BrowserOdpManager } from './odp/odp_manager.browser'; import * as commonExports from './common_exports'; import { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.react_native'; +import { createOdpManager } from './odp/odp_manager_factory.react_native'; +import { createVuidManager } from './vuid/vuid_manager_factory.react_native'; import 'fast-text-encoding'; import 'react-native-get-random-values'; @@ -70,53 +71,19 @@ const createInstance = function(config: Config): Client | null { logger.error(ex); } - // let eventBatchSize = config.eventBatchSize; - // let eventFlushInterval = config.eventFlushInterval; - - // if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - // logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - // eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - // } - // if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - // logger.warn( - // 'Invalid eventFlushInterval %s, defaulting to %s', - // config.eventFlushInterval, - // DEFAULT_EVENT_FLUSH_INTERVAL - // ); - // eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - // } - const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - // const eventProcessorConfig = { - // dispatcher: config.eventDispatcher || defaultEventDispatcher, - // flushInterval: eventFlushInterval, - // batchSize: eventBatchSize, - // maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - // notificationCenter, - // peristentCacheProvider: config.persistentCacheProvider, - // }; - - // const eventProcessor = createEventProcessor(eventProcessorConfig); - - const odpExplicitlyOff = config.odpOptions?.disabled === true; - if (odpExplicitlyOff) { - logger.info(enums.LOG_MESSAGES.ODP_DISABLED); - } - const { clientEngine, clientVersion } = config; const optimizelyOptions = { - clientEngine: enums.REACT_NATIVE_JS_CLIENT_ENGINE, ...config, - // eventProcessor, + clientEngine: clientEngine || enums.REACT_NATIVE_JS_CLIENT_ENGINE, + clientVersion: clientVersion || enums.CLIENT_VERSION, logger, errorHandler, notificationCenter, isValidInstance: isValidInstance, - odpManager: odpExplicitlyOff ? undefined - :BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; // If client engine is react, convert it to react native. @@ -147,6 +114,8 @@ export { createPollingProjectConfigManager, createForwardingEventProcessor, createBatchEventProcessor, + createOdpManager, + createVuidManager, }; export * from './common_exports'; @@ -164,6 +133,8 @@ export default { createPollingProjectConfigManager, createForwardingEventProcessor, createBatchEventProcessor, + createOdpManager, + createVuidManager, }; export * from './export_types'; diff --git a/lib/odp/constant.ts b/lib/odp/constant.ts new file mode 100644 index 000000000..c33f3f0c9 --- /dev/null +++ b/lib/odp/constant.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum ODP_USER_KEY { + VUID = 'vuid', + FS_USER_ID = 'fs_user_id', + FS_USER_ID_ALIAS = 'fs-user-id', +} + +export enum ODP_EVENT_ACTION { + IDENTIFIED = 'identified', + INITIALIZED = 'client_initialized', +} + +export const ODP_DEFAULT_EVENT_TYPE = 'fullstack'; diff --git a/lib/odp/event_manager/event_api_manager.browser.ts b/lib/odp/event_manager/event_api_manager.browser.ts deleted file mode 100644 index 26ed98136..000000000 --- a/lib/odp/event_manager/event_api_manager.browser.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { OdpEvent } from './odp_event'; -import { OdpEventApiManager } from './odp_event_api_manager'; -import { LogLevel } from '../../modules/logging'; -import { OdpConfig } from '../odp_config'; -import { HttpMethod } from '../../utils/http_request_handler/http'; - -const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; - -const pixelApiPath = 'v2/zaius.gif'; - -export class BrowserOdpEventApiManager extends OdpEventApiManager { - protected shouldSendEvents(events: OdpEvent[]): boolean { - if (events.length <= 1) { - return true; - } - this.getLogger().log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (browser only supports batch size 1)`); - return false; - } - - private getPixelApiEndpoint(odpConfig: OdpConfig): string { - const pixelUrl = odpConfig.pixelUrl; - const pixelApiEndpoint = new URL(pixelApiPath, pixelUrl).href; - return pixelApiEndpoint; - } - - protected generateRequestData( - odpConfig: OdpConfig, - events: OdpEvent[] - ): { method: HttpMethod; endpoint: string; headers: { [key: string]: string }; data: string } { - const pixelApiEndpoint = this.getPixelApiEndpoint(odpConfig); - - const apiKey = odpConfig.apiKey; - const method = 'GET'; - const event = events[0]; - const url = new URL(pixelApiEndpoint); - event.identifiers.forEach((v, k) => { - url.searchParams.append(k, v); - }); - event.data.forEach((v, k) => { - url.searchParams.append(k, v as string); - }); - url.searchParams.append('tracker_id', apiKey); - url.searchParams.append('event_type', event.type); - url.searchParams.append('vdl_action', event.action); - const endpoint = url.toString(); - return { - method, - endpoint, - headers: {}, - data: '', - }; - } -} diff --git a/lib/odp/event_manager/event_api_manager.node.ts b/lib/odp/event_manager/event_api_manager.node.ts deleted file mode 100644 index 3bf1f2ad4..000000000 --- a/lib/odp/event_manager/event_api_manager.node.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { OdpConfig } from '../odp_config'; -import { OdpEvent } from './odp_event' -import { OdpEventApiManager } from './odp_event_api_manager'; -import { HttpMethod } from '../../utils/http_request_handler/http'; - -export class NodeOdpEventApiManager extends OdpEventApiManager { - protected shouldSendEvents(events: OdpEvent[]): boolean { - return true; - } - - protected generateRequestData( - odpConfig: OdpConfig, - events: OdpEvent[] - ): { method: HttpMethod; endpoint: string; headers: { [key: string]: string }; data: string } { - - const { apiHost, apiKey } = odpConfig; - - return { - method: 'POST', - endpoint: `${apiHost}/v3/events`, - headers: { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - }, - data: JSON.stringify(events, this.replacer), - }; - } - - private replacer(_: unknown, value: unknown) { - if (value instanceof Map) { - return Object.fromEntries(value); - } else { - return value; - } - } -} diff --git a/lib/odp/event_manager/event_manager.browser.ts b/lib/odp/event_manager/event_manager.browser.ts deleted file mode 100644 index 4151c9b68..000000000 --- a/lib/odp/event_manager/event_manager.browser.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { IOdpEventManager, OdpEventManager } from './odp_event_manager'; -import { LogLevel } from '../../modules/logging'; -import { OdpEvent } from './odp_event'; - -const DEFAULT_BROWSER_QUEUE_SIZE = 100; - -export class BrowserOdpEventManager extends OdpEventManager implements IOdpEventManager { - protected initParams( - batchSize: number | undefined, - queueSize: number | undefined, - flushInterval: number | undefined - ): void { - this.queueSize = queueSize || DEFAULT_BROWSER_QUEUE_SIZE; - - // disable event batching for browser - this.batchSize = 1; - this.flushInterval = 0; - - if (typeof batchSize !== 'undefined' && batchSize !== 1) { - this.getLogger().log(LogLevel.WARNING, 'ODP event batch size must be 1 in the browser.'); - } - - if (typeof flushInterval !== 'undefined' && flushInterval !== 0) { - this.getLogger().log(LogLevel.WARNING, 'ODP event flush interval must be 0 in the browser.'); - } - } - - protected discardEventsIfNeeded(): void { - // in Browser/client-side context, give debug message but leave events in queue - this.getLogger().log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); - } - - protected hasNecessaryIdentifiers = (event: OdpEvent): boolean => event.identifiers.size >= 0; -} diff --git a/lib/odp/event_manager/event_manager.node.ts b/lib/odp/event_manager/event_manager.node.ts deleted file mode 100644 index e057755a9..000000000 --- a/lib/odp/event_manager/event_manager.node.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { OdpEvent } from './odp_event'; -import { IOdpEventManager, OdpEventManager } from './odp_event_manager'; -import { LogLevel } from '../../modules/logging'; - -const DEFAULT_BATCH_SIZE = 10; -const DEFAULT_FLUSH_INTERVAL_MSECS = 1000; -const DEFAULT_SERVER_QUEUE_SIZE = 10000; - -export class NodeOdpEventManager extends OdpEventManager implements IOdpEventManager { - protected initParams( - batchSize: number | undefined, - queueSize: number | undefined, - flushInterval: number | undefined - ): void { - this.queueSize = queueSize || DEFAULT_SERVER_QUEUE_SIZE; - this.batchSize = batchSize || DEFAULT_BATCH_SIZE; - - if (flushInterval === 0) { - // disable event batching - this.batchSize = 1; - this.flushInterval = 0; - } else { - this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; - } - } - - protected discardEventsIfNeeded(): void { - // if Node/server-side context, empty queue items before ready state - this.getLogger().log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); - this.queue = new Array<OdpEvent>(); - } - - protected hasNecessaryIdentifiers = (event: OdpEvent): boolean => event.identifiers.size >= 1; -} diff --git a/lib/odp/event_manager/odp_event.ts b/lib/odp/event_manager/odp_event.ts index e777789bc..062798d1b 100644 --- a/lib/odp/event_manager/odp_event.ts +++ b/lib/odp/event_manager/odp_event.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/odp/event_manager/odp_event_api_manager.spec.ts b/lib/odp/event_manager/odp_event_api_manager.spec.ts new file mode 100644 index 000000000..8f6a07fd2 --- /dev/null +++ b/lib/odp/event_manager/odp_event_api_manager.spec.ts @@ -0,0 +1,206 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; + +import { DefaultOdpEventApiManager, eventApiRequestGenerator, pixelApiRequestGenerator } from './odp_event_api_manager'; +import { OdpEvent } from './odp_event'; +import { OdpConfig } from '../odp_config'; + +const data1 = new Map<string, unknown>(); +data1.set('key11', 'value-1'); +data1.set('key12', true); +data1.set('key13', 3.5); +data1.set('key14', null); + +const data2 = new Map<string, unknown>(); + +data2.set('key2', 'value-2'); + +const ODP_EVENTS = [ + new OdpEvent('t1', 'a1', new Map([['id-key-1', 'id-value-1']]), data1), + new OdpEvent('t2', 'a2', new Map([['id-key-2', 'id-value-2']]), data2), +]; + +const API_KEY = 'test-api-key'; +const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; + +const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); + +import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; + +describe('DefaultOdpEventApiManager', () => { + it('should generate the event request using the correct odp config and event', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.resolve({ + statusCode: 200, + body: '', + headers: {}, + }), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + manager.sendEvents(odpConfig, ODP_EVENTS); + + expect(requestGenerator.mock.calls[0][0]).toEqual(odpConfig); + expect(requestGenerator.mock.calls[0][1]).toEqual(ODP_EVENTS); + }); + + it('should send the correct request using the request handler', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.resolve({ + statusCode: 200, + body: '', + headers: {}, + }), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + manager.sendEvents(odpConfig, ODP_EVENTS); + + expect(mockRequestHandler.makeRequest.mock.calls[0][0]).toEqual('https://odp.example.com/v3/events'); + expect(mockRequestHandler.makeRequest.mock.calls[0][1]).toEqual({ + 'x-api-key': 'test-api', + }); + expect(mockRequestHandler.makeRequest.mock.calls[0][2]).toEqual('PATCH'); + expect(mockRequestHandler.makeRequest.mock.calls[0][3]).toEqual('event-data'); + }); + + it('should return a promise that fails if the requestHandler response promise fails', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.reject(new Error('Request failed')), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + const response = manager.sendEvents(odpConfig, ODP_EVENTS); + + await expect(response).rejects.toThrow('Request failed'); + }); + + it('should return a promise that resolves with correct response code from the requestHandler', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.resolve({ + statusCode: 226, + body: '', + headers: {}, + }), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + const response = manager.sendEvents(odpConfig, ODP_EVENTS); + + await expect(response).resolves.not.toThrow(); + const statusCode = await response.then((r) => r.statusCode); + expect(statusCode).toBe(226); + }); +}); + +describe('pixelApiRequestGenerator', () => { + it('should generate the correct request for the pixel API using only the first event', () => { + const request = pixelApiRequestGenerator(odpConfig, ODP_EVENTS); + expect(request.method).toBe('GET'); + const endpoint = new URL(request.endpoint); + expect(endpoint.origin).toBe(PIXEL_URL); + expect(endpoint.pathname).toBe('/v2/zaius.gif'); + expect(endpoint.searchParams.get('id-key-1')).toBe('id-value-1'); + expect(endpoint.searchParams.get('key11')).toBe('value-1'); + expect(endpoint.searchParams.get('key12')).toBe('true'); + expect(endpoint.searchParams.get('key13')).toBe('3.5'); + expect(endpoint.searchParams.get('key14')).toBe('null'); + expect(endpoint.searchParams.get('tracker_id')).toBe(API_KEY); + expect(endpoint.searchParams.get('event_type')).toBe('t1'); + expect(endpoint.searchParams.get('vdl_action')).toBe('a1'); + + expect(request.headers).toEqual({}); + expect(request.data).toBe(''); + }); +}); + +describe('eventApiRequestGenerator', () => { + it('should generate the correct request for the event API using all events', () => { + const request = eventApiRequestGenerator(odpConfig, ODP_EVENTS); + expect(request.method).toBe('POST'); + expect(request.endpoint).toBe('https://odp.example.com/v3/events'); + expect(request.headers).toEqual({ + 'Content-Type': 'application/json', + 'x-api-key': API_KEY, + }); + + const data = JSON.parse(request.data); + expect(data).toEqual([ + { + type: 't1', + action: 'a1', + identifiers: { + 'id-key-1': 'id-value-1', + }, + data: { + key11: 'value-1', + key12: true, + key13: 3.5, + key14: null, + }, + }, + { + type: 't2', + action: 'a2', + identifiers: { + 'id-key-2': 'id-value-2', + }, + data: { + key2: 'value-2', + }, + }, + ]); + }); +}); diff --git a/lib/odp/event_manager/odp_event_api_manager.ts b/lib/odp/event_manager/odp_event_api_manager.ts index 2a5249a28..8ea4f7060 100644 --- a/lib/odp/event_manager/odp_event_api_manager.ts +++ b/lib/odp/event_manager/odp_event_api_manager.ts @@ -14,103 +14,92 @@ * limitations under the License. */ -import { LogHandler, LogLevel } from '../../modules/logging'; +import { LoggerFacade } from '../../modules/logging'; import { OdpEvent } from './odp_event'; import { HttpMethod, RequestHandler } from '../../utils/http_request_handler/http'; import { OdpConfig } from '../odp_config'; -const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; - -/** - * Manager for communicating with the Optimizely Data Platform REST API - */ -export interface IOdpEventApiManager { - sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<boolean>; +export type EventDispatchResponse = { + statusCode?: number; +}; +export interface OdpEventApiManager { + sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<EventDispatchResponse>; } -/** - * Concrete implementation for accessing the ODP REST API - */ -export abstract class OdpEventApiManager implements IOdpEventApiManager { - /** - * Handler for recording execution logs - * @private - */ - private readonly logger: LogHandler; - - /** - * Handler for making external HTTP/S requests - * @private - */ - private readonly requestHandler: RequestHandler; +export type EventRequest = { + method: HttpMethod; + endpoint: string; + headers: Record<string, string>; + data: string; +} - /** - * Creates instance to access Optimizely Data Platform (ODP) REST API - * @param requestHandler Desired request handler for testing - * @param logger Collect and record events/errors for this GraphQL implementation - */ - constructor(requestHandler: RequestHandler, logger: LogHandler) { +export type EventRequestGenerator = (odpConfig: OdpConfig, events: OdpEvent[]) => EventRequest; +export class DefaultOdpEventApiManager implements OdpEventApiManager { + private logger?: LoggerFacade; + private requestHandler: RequestHandler; + private requestGenerator: EventRequestGenerator; + + constructor( + requestHandler: RequestHandler, + requestDataGenerator: EventRequestGenerator, + logger?: LoggerFacade + ) { this.requestHandler = requestHandler; + this.requestGenerator = requestDataGenerator; this.logger = logger; } - getLogger(): LogHandler { - return this.logger; - } - - /** - * Service for sending ODP events to REST API - * @param events ODP events to send - * @returns Retry is true - if network or server error (5xx), otherwise false - */ - async sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<boolean> { - let shouldRetry = false; - + async sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<EventDispatchResponse> { if (events.length === 0) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (no events)`); - return shouldRetry; + return {}; } - if (!this.shouldSendEvents(events)) { - return shouldRetry; - } + const { method, endpoint, headers, data } = this.requestGenerator(odpConfig, events); - const { method, endpoint, headers, data } = this.generateRequestData(odpConfig, events); - - let statusCode = 0; - try { - const request = this.requestHandler.makeRequest(endpoint, headers, method, data); - const response = await request.responsePromise; - statusCode = response.statusCode ?? statusCode; - } catch (err) { - let message = 'network error'; - if (err instanceof Error) { - message = (err as Error).message; - } - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${message})`); - shouldRetry = true; - } - - if (statusCode >= 400) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${statusCode})`); - } - - if (statusCode >= 500) { - shouldRetry = true; - } - - return shouldRetry; + const request = this.requestHandler.makeRequest(endpoint, headers, method, data); + return request.responsePromise; } +} - protected abstract shouldSendEvents(events: OdpEvent[]): boolean; +export const pixelApiRequestGenerator: EventRequestGenerator = (odpConfig: OdpConfig, events: OdpEvent[]): EventRequest => { + const pixelApiPath = 'v2/zaius.gif'; + const pixelApiEndpoint = new URL(pixelApiPath, odpConfig.pixelUrl); + + const apiKey = odpConfig.apiKey; + const method = 'GET'; + const event = events[0]; + + event.identifiers.forEach((v, k) => { + pixelApiEndpoint.searchParams.append(k, v); + }); + event.data.forEach((v, k) => { + pixelApiEndpoint.searchParams.append(k, v as string); + }); + pixelApiEndpoint.searchParams.append('tracker_id', apiKey); + pixelApiEndpoint.searchParams.append('event_type', event.type); + pixelApiEndpoint.searchParams.append('vdl_action', event.action); + const endpoint = pixelApiEndpoint.toString(); + + return { + method, + endpoint, + headers: {}, + data: '', + }; +} - protected abstract generateRequestData( - odpConfig: OdpConfig, - events: OdpEvent[] - ): { - method: HttpMethod; - endpoint: string; - headers: { [key: string]: string }; - data: string; +export const eventApiRequestGenerator: EventRequestGenerator = (odpConfig: OdpConfig, events: OdpEvent[]): EventRequest => { + const { apiHost, apiKey } = odpConfig; + + return { + method: 'POST', + endpoint: `${apiHost}/v3/events`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey, + }, + data: JSON.stringify(events, (_: unknown, value: unknown) => { + return value instanceof Map ? Object.fromEntries(value) : value; + }), }; } diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts new file mode 100644 index 000000000..dfe8d496a --- /dev/null +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -0,0 +1,940 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; +import { DefaultOdpEventManager } from './odp_event_manager'; +import { getMockRepeater } from '../../tests/mock/mock_repeater'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { ServiceState } from '../../service'; +import { exhaustMicrotasks } from '../../tests/testUtils'; +import { OdpEvent } from './odp_event'; +import { OdpConfig } from '../odp_config'; +import { EventDispatchResponse } from './odp_event_api_manager'; +import { advanceTimersByTime } from '../../../tests/testUtils'; + +const API_KEY = 'test-api-key'; +const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; +const SEGMENTS_TO_CHECK = ['segment1', 'segment2']; + +const config = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, SEGMENTS_TO_CHECK); + +const makeEvent = (id: number) => { + const identifiers = new Map<string, string>(); + identifiers.set('identifier1', 'value1-' + id); + identifiers.set('identifier2', 'value2-' + id); + + const data = new Map<string, unknown>(); + data.set('data1', 'data-value1-' + id); + data.set('data2', id); + + return new OdpEvent('test-type-' + id, 'test-action-' + id, identifiers, data); +}; + +const getMockApiManager = () => { + return { + sendEvents: vi.fn(), + }; +}; + +describe('DefaultOdpEventManager', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should be in new state after construction', () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + expect(odpEventManager.getState()).toBe(ServiceState.New); + }); + + it('should stay in starting state if started with a odpIntegationConfig and not resolve or reject onRunning', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + const onRunningHandler = vi.fn(); + odpEventManager.onRunning().then(onRunningHandler, onRunningHandler); + + odpEventManager.start(); + expect(odpEventManager.getState()).toBe(ServiceState.Starting); + + await exhaustMicrotasks(); + + expect(odpEventManager.getState()).toBe(ServiceState.Starting); + expect(onRunningHandler).not.toHaveBeenCalled(); + }); + + it('should move to running state and resolve onRunning() is start() is called after updateConfig()', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: false, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + }); + + it('should move to running state and resolve onRunning() is updateConfig() is called after start()', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.start(); + + odpEventManager.updateConfig({ + integrated: false, + }); + + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + }); + + it('should queue events until batchSize is reached', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for (let i = 0; i < 9; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + events.push(makeEvent(9)); + odpEventManager.sendEvent(events[9]); + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + }); + + it('should send events immediately asynchronously if batchSize is 1', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + for (let i = 0; i < 10; i++) { + const event = makeEvent(i); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i + 1, config, [event]); + } + }); + + it('drops events and logs if the state is not running', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + expect(odpEventManager.getState()).toBe(ServiceState.New); + + const event = makeEvent(0); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('drops events and logs if odpIntegrationConfig is not integrated', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: false, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = makeEvent(0); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('drops event and logs if there is no identifier', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = new OdpEvent('test-type', 'test-action', new Map(), new Map()); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('accepts string, number, boolean, and null values for data', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const data = new Map<string, unknown>(); + data.set('string', 'string-value'); + data.set('number', 123); + data.set('boolean', true); + data.set('null', null); + + const event = new OdpEvent('test-type', 'test-action', new Map([['k', 'v']]), data); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, [event]); + }); + + it('should drop event and log if data contains values other than string, number, boolean, or null', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const data = new Map<string, unknown>(); + data.set('string', 'string-value'); + data.set('number', 123); + data.set('boolean', true); + data.set('null', null); + data.set('invalid', new Date()); + + const event = new OdpEvent('test-type', 'test-action', new Map([['k', 'v']]), data); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('should drop event and log if action is empty', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = new OdpEvent('test-type', '', new Map([['k', 'v']]), new Map([['k', 'v']])); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('should use fullstack as type if type is empty', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = new OdpEvent('', 'test-action', new Map([['k', 'v']]), new Map([['k', 'v']])); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents.mock.calls[0][1][0].type).toBe('fullstack'); + }); + + it('should transform identifiers with keys FS-USER-ID, fs-user-id and FS_USER_ID to fs_user_id', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 3, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event1 = new OdpEvent('test-type', 'test-action', new Map([['FS-USER-ID', 'value1']]), new Map([['k', 'v']])); + const event2 = new OdpEvent('test-type', 'test-action', new Map([['fs-user-id', 'value2']]), new Map([['k', 'v']])); + const event3 = new OdpEvent('test-type', 'test-action', new Map([['FS_USER_ID', 'value3']]), new Map([['k', 'v']])); + + odpEventManager.sendEvent(event1); + odpEventManager.sendEvent(event2); + odpEventManager.sendEvent(event3); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents.mock.calls[0][1][0].identifiers.get('fs_user_id')).toBe('value1'); + expect(apiManager.sendEvents.mock.calls[0][1][1].identifiers.get('fs_user_id')).toBe('value2'); + expect(apiManager.sendEvents.mock.calls[0][1][2].identifiers.get('fs_user_id')).toBe('value3'); + }); + + it('should start the repeater when the first event is sent', async () => { + const repeater = getMockRepeater(); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: getMockApiManager(), + batchSize: 300, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + expect(repeater.start).not.toHaveBeenCalled(); + + for(let i = 0; i < 10; i++) { + odpEventManager.sendEvent(makeEvent(i)); + await exhaustMicrotasks(); + expect(repeater.start).toHaveBeenCalledTimes(1); + } + }); + + it('should flush the queue when the repeater triggers', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + await repeater.execute(0); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + }); + + it('should reset the repeater after flush', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + expect(repeater.reset).not.toHaveBeenCalled(); + + await repeater.execute(0); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + expect(repeater.reset).toHaveBeenCalledTimes(1); + }); + + it('should retry specified number of times with backoff if apiManager.sendEvents returns a rejecting promise', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('Failed to dispatch events'))); + + const backoffController = { + backoff: vi.fn().mockReturnValue(666), + reset: vi.fn(), + }; + + const maxRetries = 5; + const retryConfig = { + maxRetries, + backoffProvider: () => backoffController, + }; + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: retryConfig, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + repeater.execute(0); + for(let i = 1; i <= maxRetries; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(666); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i, config, events); + expect(backoffController.backoff).toHaveBeenCalledTimes(i); + } + }); + + it('should retry specified number of times with backoff if apiManager returns 5xx', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockReturnValue(Promise.resolve({ statusCode: 500 })); + + const backoffController = { + backoff: vi.fn().mockReturnValue(666), + reset: vi.fn(), + }; + + const maxRetries = 5; + const retryConfig = { + maxRetries, + backoffProvider: () => backoffController, + }; + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: retryConfig, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + repeater.execute(0); + for(let i = 1; i <= maxRetries; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(666); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i, config, events); + expect(backoffController.backoff).toHaveBeenCalledTimes(i); + } + }); + + it('should log error if event sends fails even after retry', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('Failed to dispatch events'))); + + const backoffController = { + backoff: vi.fn().mockReturnValue(666), + reset: vi.fn(), + }; + + const maxRetries = 5; + const retryConfig = { + maxRetries, + backoffProvider: () => backoffController, + }; + + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: retryConfig, + }); + + odpEventManager.setLogger(logger); + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + repeater.execute(0); + for(let i = 1; i <= maxRetries; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(666); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i, config, events); + expect(backoffController.backoff).toHaveBeenCalledTimes(i); + } + + await exhaustMicrotasks(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('flushes the queue with old config if updateConfig is called with a new config', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + const newConfig = new OdpConfig('new-api-key', 'https://new-odp.example.com/', 'https://new-odp.pixel.com/', ['new-segment']); + odpEventManager.updateConfig({ + integrated: true, + odpConfig: newConfig, + }); + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledOnce(); + expect(apiManager.sendEvents).toHaveBeenCalledWith(config, events); + }); + + it('uses the new config after updateConfig is called', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + const newConfig = new OdpConfig('new-api-key', 'https://new-odp.example.com/', 'https://new-odp.pixel.com/', ['new-segment']); + odpEventManager.updateConfig({ + integrated: true, + odpConfig: newConfig, + }); + + const newEvents: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + newEvents.push(makeEvent(i + 10)); + odpEventManager.sendEvent(newEvents[i]); + } + + repeater.execute(0); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(2); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(2, newConfig, newEvents); + }); + + it('should reject onRunning() if stop() is called in new state', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.stop(); + await expect(odpEventManager.onRunning()).rejects.toThrow(); + }); + + it('should flush the queue and reset the repeater if stop() is called in running state', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + odpEventManager.stop(); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenCalledWith(config, events); + expect(repeater.reset).toHaveBeenCalledTimes(1); + }); + + it('resolve onTerminated() and go to Terminated state if stop() is called in running state', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + odpEventManager.stop(); + await expect(odpEventManager.onTerminated()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Terminated); + }); +}); diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 2b4d69e57..9db9086a4 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -14,440 +14,198 @@ * limitations under the License. */ -import { LogHandler, LogLevel } from '../../modules/logging'; - -import { uuid } from '../../utils/fns'; -import { ERROR_MESSAGES, ODP_USER_KEY, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION } from '../../utils/enums'; - import { OdpEvent } from './odp_event'; -import { OdpConfig } from '../odp_config'; -import { IOdpEventApiManager } from './odp_event_api_manager'; -import { invalidOdpDataFound } from '../odp_utils'; -import { IUserAgentParser } from '../ua_parser/user_agent_parser'; -import { scheduleMicrotask } from '../../utils/microtask'; - -const MAX_RETRIES = 3; - -/** - * Event dispatcher's execution states - */ -export enum Status { - Stopped, - Running, +import { OdpConfig, OdpIntegrationConfig } from '../odp_config'; +import { OdpEventApiManager } from './odp_event_api_manager'; +import { BaseService, Service, ServiceState, StartupLog } from '../../service'; +import { BackoffController, Repeater } from '../../utils/repeater/repeater'; +import { Producer } from '../../utils/type'; +import { runWithRetry } from '../../utils/executor/backoff_retry_runner'; +import { isSuccessStatusCode } from '../../utils/http_request_handler/http_util'; +import { ERROR_MESSAGES } from '../../utils/enums'; +import { ODP_DEFAULT_EVENT_TYPE, ODP_USER_KEY } from '../constant'; + +export interface OdpEventManager extends Service { + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; + sendEvent(event: OdpEvent): void; } -/** - * Manager for persisting events to the Optimizely Data Platform (ODP) - */ -export interface IOdpEventManager { - updateSettings(odpConfig: OdpConfig): void; - - start(): void; - - stop(): Promise<void>; +export type RetryConfig = { + maxRetries: number; + backoffProvider: Producer<BackoffController>; +} - registerVuid(vuid: string): void; +export type OdpEventManagerConfig = { + repeater: Repeater, + apiManager: OdpEventApiManager, + batchSize: number, + startUpLogs?: StartupLog[], + retryConfig: RetryConfig, +}; - identifyUser(userId?: string, vuid?: string): void; +export class DefaultOdpEventManager extends BaseService implements OdpEventManager { + private queue: OdpEvent[] = []; + private repeater: Repeater; + private odpIntegrationConfig?: OdpIntegrationConfig; + private apiManager: OdpEventApiManager; + private batchSize: number; - sendEvent(event: OdpEvent): void; + private retryConfig: RetryConfig; - flush(retry?: boolean): void; -} + constructor(config: OdpEventManagerConfig) { + super(config.startUpLogs); -/** - * Concrete implementation of a manager for persisting events to the Optimizely Data Platform - */ -export abstract class OdpEventManager implements IOdpEventManager { - /** - * Current state of the event processor - */ - status: Status = Status.Stopped; - - /** - * Queue for holding all events to be eventually dispatched - * @protected - */ - protected queue = new Array<OdpEvent>(); - - /** - * Identifier of the currently running timeout so clearCurrentTimeout() can be called - * @private - */ - private timeoutId?: NodeJS.Timeout | number; - - /** - * ODP configuration settings for identifying the target API and segments - * @private - */ - private odpConfig?: OdpConfig; - - /** - * REST API Manager used to send the events - * @private - */ - private readonly apiManager: IOdpEventApiManager; - - /** - * Handler for recording execution logs - * @private - */ - private readonly logger: LogHandler; - - /** - * Maximum queue size - * @protected - */ - protected queueSize!: number; - - /** - * Maximum number of events to process at once. Ignored in browser context - * @protected - */ - protected batchSize!: number; - - /** - * Milliseconds between setTimeout() to process new batches. Ignored in browser context - * @protected - */ - protected flushInterval!: number; - - /** - * Type of execution context eg node, js, react - * @private - */ - private readonly clientEngine: string; - - /** - * Version of the client being used - * @private - */ - private readonly clientVersion: string; - - /** - * Version of the client being used - * @private - */ - private readonly userAgentParser?: IUserAgentParser; - - private retries: number; - - - /** - * Information about the user agent - * @private - */ - private readonly userAgentData?: Map<string, unknown>; - - constructor({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - queueSize, - batchSize, - flushInterval, - userAgentParser, - retries, - }: { - odpConfig?: OdpConfig; - apiManager: IOdpEventApiManager; - logger: LogHandler; - clientEngine: string; - clientVersion: string; - queueSize?: number; - batchSize?: number; - flushInterval?: number; - userAgentParser?: IUserAgentParser; - retries?: number; - }) { - this.apiManager = apiManager; - this.logger = logger; - this.clientEngine = clientEngine; - this.clientVersion = clientVersion; - this.initParams(batchSize, queueSize, flushInterval); - this.status = Status.Stopped; - this.userAgentParser = userAgentParser; - this.retries = retries || MAX_RETRIES; - - if (userAgentParser) { - const { os, device } = userAgentParser.parseUserAgentInfo(); - - const userAgentInfo: Record<string, unknown> = { - 'os': os.name, - 'os_version': os.version, - 'device_type': device.type, - 'model': device.model, - }; - - this.userAgentData = new Map<string, unknown>( - Object.entries(userAgentInfo).filter(([key, value]) => value != null && value != undefined) - ); - } + this.apiManager = config.apiManager; + this.batchSize = config.batchSize; + this.retryConfig = config.retryConfig; - if (odpConfig) { - this.updateSettings(odpConfig); - } + this.repeater = config.repeater; + this.repeater.setTask(() => this.flush()); } - protected abstract initParams( - batchSize: number | undefined, - queueSize: number | undefined, - flushInterval: number | undefined - ): void; - - /** - * Update ODP configuration settings. - * @param newConfig New configuration to apply - */ - updateSettings(odpConfig: OdpConfig): void { - // do nothing if config did not change - if (this.odpConfig && this.odpConfig.equals(odpConfig)) { - return; + private async executeDispatch(odpConfig: OdpConfig, batch: OdpEvent[]): Promise<unknown> { + const res = await this.apiManager.sendEvents(odpConfig, batch); + if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { + // TODO: replace message with imported constants + return Promise.reject(new Error(`Failed to dispatch events: ${res.statusCode}`)); } - - this.flush(); - this.odpConfig = odpConfig; + return await Promise.resolve(res); } - /** - * Cleans up all pending events; - */ - flush(): void { - this.processQueue(true); - } - - /** - * Start the event manager - */ - start(): void { - if (!this.odpConfig) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + private async flush(): Promise<unknown> { + if (!this.odpIntegrationConfig || !this.odpIntegrationConfig.integrated) { return; } - this.status = Status.Running; - - // no need of periodic flush if batchSize is 1 - if (this.batchSize > 1) { - this.setNewTimeout(); - } - } - - /** - * Drain the queue sending all remaining events in batches then stop processing - */ - async stop(): Promise<void> { - this.logger.log(LogLevel.DEBUG, 'Stop requested.'); + const odpConfig = this.odpIntegrationConfig.odpConfig; - this.flush(); - this.clearCurrentTimeout(); - this.status = Status.Stopped; - this.logger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', this.queue.length); - } + const batch = this.queue; + this.queue = []; - /** - * Register a new visitor user id (VUID) in ODP - * @param vuid Visitor User ID to send - */ - registerVuid(vuid: string): void { - const identifiers = new Map<string, string>(); - identifiers.set(ODP_USER_KEY.VUID, vuid); + // as the queue has been emptied, stop repeating flush + // until more events become available + this.repeater.reset(); - const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, identifiers); - this.sendEvent(event); + return runWithRetry( + () => this.executeDispatch(odpConfig, batch), this.retryConfig.backoffProvider(), this.retryConfig.maxRetries + ).result.catch((err) => { + // TODO: replace with imported constants + this.logger?.error('failed to send odp events', err); + }); } - /** - * Associate a full-stack userid with an established VUID - * @param {string} userId (Optional) Full-stack User ID - * @param {string} vuid (Optional) Visitor User ID - */ - identifyUser(userId?: string, vuid?: string): void { - const identifiers = new Map<string, string>(); - if (!userId && !vuid) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_UID_MISSING); + start(): void { + if (!this.isNew) { return; } - if (vuid) { - identifiers.set(ODP_USER_KEY.VUID, vuid); - } - - if (userId) { - identifiers.set(ODP_USER_KEY.FS_USER_ID, userId); - } - - const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.IDENTIFIED, identifiers); - this.sendEvent(event); - } - - /** - * Send an event to ODP via dispatch queue - * @param event ODP Event to forward - */ - sendEvent(event: OdpEvent): void { - if (invalidOdpDataFound(event.data)) { - this.logger.log(LogLevel.ERROR, 'Event data found to be invalid.'); + super.start(); + if (this.odpIntegrationConfig) { + this.goToRunningState(); } else { - event.data = this.augmentCommonData(event.data); - this.enqueue(event); + this.state = ServiceState.Starting; } } - /** - * Add a new event to the main queue - * @param event ODP Event to be queued - * @private - */ - private enqueue(event: OdpEvent): void { - if (this.status === Status.Stopped) { - this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.'); + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void { + if (this.isDone()) { return; } - if (!this.hasNecessaryIdentifiers(event)) { - this.logger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.'); + if (this.isNew()) { + this.odpIntegrationConfig = odpIntegrationConfig; return; } - if (this.queue.length >= this.queueSize) { - this.logger.log( - LogLevel.WARNING, - 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', - this.queue.length - ); + if (this.isStarting()) { + this.odpIntegrationConfig = odpIntegrationConfig; + this.goToRunningState(); return; } - this.queue.push(event); - this.processQueue(); + // already running, flush the queue using the previous config first before updating the config + this.flush(); + this.odpIntegrationConfig = odpIntegrationConfig; } - protected abstract hasNecessaryIdentifiers(event: OdpEvent): boolean; + private goToRunningState() { + this.state = ServiceState.Running; + this.startPromise.resolve(); + } - /** - * Process events in the main queue - * @param shouldFlush Flush all events regardless of available queue event count - * @private - */ - private processQueue(shouldFlush = false): void { - if (this.status !== Status.Running) { + stop(): void { + if (this.isDone()) { return; } - - if (shouldFlush) { - // clear the queue completely - this.clearCurrentTimeout(); - while (this.queueContainsItems()) { - this.makeAndSend1Batch(); - } - } else if (this.queueHasBatches()) { - // Check if queue has a full batch available - this.clearCurrentTimeout(); - - while (this.queueHasBatches()) { - this.makeAndSend1Batch(); - } - } - - // no need for periodic flush if batchSize is 1 - if (this.batchSize > 1) { - this.setNewTimeout(); + if (this.isNew()) { + this.startPromise.reject(new Error('odp event manager stopped before it could start')); } - } - /** - * Clear the currently running timout - * @private - */ - private clearCurrentTimeout(): void { - clearTimeout(this.timeoutId); - this.timeoutId = undefined; + this.flush(); + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); } - /** - * Start a new timeout - * @private - */ - private setNewTimeout(): void { - if (this.timeoutId !== undefined) { + sendEvent(event: OdpEvent): void { + if (!this.isRunning()) { + this.logger?.error('ODP event manager is not running.'); return; } - this.timeoutId = setTimeout(() => this.processQueue(true), this.flushInterval); - } - /** - * Make a batch and send it to ODP - * @private - */ - private makeAndSend1Batch(): void { - if (!this.odpConfig) { - return; + if (!this.odpIntegrationConfig?.integrated) { + this.logger?.error(ERROR_MESSAGES.ODP_NOT_INTEGRATED); + return; } - const batch = this.queue.splice(0, this.batchSize); - - const odpConfig = this.odpConfig; - - if (batch.length > 0) { - // put sending the event on another event loop - scheduleMicrotask(async () => { - let shouldRetry: boolean; - let attemptNumber = 0; - do { - shouldRetry = await this.apiManager.sendEvents(odpConfig, batch); - attemptNumber += 1; - } while (shouldRetry && attemptNumber < this.retries); - }) + if (event.identifiers.size === 0) { + this.logger?.error('ODP events should have at least one key-value pair in identifiers.'); + return; } - } - /** - * Check if main queue has any full/even batches available - * @returns True if there are event batches available in the queue otherwise False - * @private - */ - private queueHasBatches(): boolean { - return this.queueContainsItems() && this.queue.length % this.batchSize === 0; - } + if (!this.isDataValid(event.data)) { + this.logger?.error('Event data found to be invalid.'); + return; + } - /** - * Check if main queue has any items - * @returns True if there are any events in the queue otherwise False - * @private - */ - private queueContainsItems(): boolean { - return this.queue.length > 0; - } + if (!event.action ) { + this.logger?.error('Event action invalid.'); + return; + } - protected abstract discardEventsIfNeeded(): void; + if (event.type === '') { + event.type = ODP_DEFAULT_EVENT_TYPE; + } - /** - * Add additional common data including an idempotent ID and execution context to event data - * @param sourceData Existing event data to augment - * @returns Augmented event data - * @private - */ - private augmentCommonData(sourceData: Map<string, unknown>): Map<string, unknown> { - const data = new Map<string, unknown>(this.userAgentData); + Array.from(event.identifiers.entries()).forEach(([key, value]) => { + // Catch for fs-user-id, FS-USER-ID, and FS_USER_ID and assign value to fs_user_id identifier. + if ( + ODP_USER_KEY.FS_USER_ID_ALIAS === key.toLowerCase() || + ODP_USER_KEY.FS_USER_ID === key.toLowerCase() + ) { + event.identifiers.delete(key); + event.identifiers.set(ODP_USER_KEY.FS_USER_ID, value); + } + }); - data.set('idempotence_id', uuid()); - data.set('data_source_type', 'sdk'); - data.set('data_source', this.clientEngine); - data.set('data_source_version', this.clientVersion); - - sourceData.forEach((value, key) => data.set(key, value)); - return data; + this.processEvent(event); } - protected getLogger(): LogHandler { - return this.logger; + private isDataValid(data: Map<string, any>): boolean { + const validTypes: string[] = ['string', 'number', 'boolean']; + return Array.from(data.values()).reduce( + (valid, value) => valid && (value === null || validTypes.includes(typeof value)), + true, + ); } - getQueue(): OdpEvent[] { - return this.queue; + private processEvent(event: OdpEvent): void { + this.queue.push(event); + + if (this.queue.length === this.batchSize) { + this.flush(); + } else if (!this.repeater.isRunning()) { + this.repeater.start(); + } } } diff --git a/lib/odp/odp_manager.browser.ts b/lib/odp/odp_manager.browser.ts deleted file mode 100644 index 7168b5822..000000000 --- a/lib/odp/odp_manager.browser.ts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright 2023-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - CLIENT_VERSION, - ERROR_MESSAGES, - JAVASCRIPT_CLIENT_ENGINE, - ODP_USER_KEY, - REQUEST_TIMEOUT_ODP_SEGMENTS_MS, - REQUEST_TIMEOUT_ODP_EVENTS_MS, - LOG_MESSAGES, -} from '../utils/enums'; -import { getLogger, LogHandler, LogLevel } from '../modules/logging'; - -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; - -import BrowserAsyncStorageCache from '../plugins/key_value_cache/browserAsyncStorageCache'; -import { BrowserLRUCache } from '../utils/lru_cache'; - -import { VuidManager } from '../plugins/vuid_manager/index'; - -import { OdpManager } from './odp_manager'; -import { OdpEvent } from './event_manager/odp_event'; -import { IOdpEventManager, OdpOptions } from '../shared_types'; -import { BrowserOdpEventApiManager } from './event_manager/event_api_manager.browser'; -import { BrowserOdpEventManager } from './event_manager/event_manager.browser'; -import { IOdpSegmentManager, OdpSegmentManager } from './segment_manager/odp_segment_manager'; -import { OdpSegmentApiManager } from './segment_manager/odp_segment_api_manager'; -import { OdpConfig, OdpIntegrationConfig } from './odp_config'; - -interface BrowserOdpManagerConfig { - clientEngine?: string, - clientVersion?: string, - logger?: LogHandler; - odpOptions?: OdpOptions; - odpIntegrationConfig?: OdpIntegrationConfig; -} - -// Client-side Browser Plugin for ODP Manager -export class BrowserOdpManager extends OdpManager { - static cache = new BrowserAsyncStorageCache(); - vuidManager?: VuidManager; - vuid?: string; - - constructor(options: { - odpIntegrationConfig?: OdpIntegrationConfig; - segmentManager: IOdpSegmentManager; - eventManager: IOdpEventManager; - logger: LogHandler; - }) { - super(options); - } - - static createInstance({ - logger, odpOptions, odpIntegrationConfig, clientEngine, clientVersion - }: BrowserOdpManagerConfig): BrowserOdpManager { - logger = logger || getLogger(); - - clientEngine = clientEngine || JAVASCRIPT_CLIENT_ENGINE; - clientVersion = clientVersion || CLIENT_VERSION; - - let odpConfig : OdpConfig | undefined = undefined; - if (odpIntegrationConfig?.integrated) { - odpConfig = odpIntegrationConfig.odpConfig; - } - - let customSegmentRequestHandler; - - if (odpOptions?.segmentsRequestHandler) { - customSegmentRequestHandler = odpOptions.segmentsRequestHandler; - } else { - customSegmentRequestHandler = new BrowserRequestHandler({ - logger, - timeout: odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS - }); - } - - let segmentManager: IOdpSegmentManager; - - if (odpOptions?.segmentManager) { - segmentManager = odpOptions.segmentManager; - } else { - segmentManager = new OdpSegmentManager( - odpOptions?.segmentsCache || - new BrowserLRUCache<string, string[]>({ - maxSize: odpOptions?.segmentsCacheSize, - timeout: odpOptions?.segmentsCacheTimeout, - }), - new OdpSegmentApiManager(customSegmentRequestHandler, logger), - logger, - odpConfig - ); - } - - let customEventRequestHandler; - - if (odpOptions?.eventRequestHandler) { - customEventRequestHandler = odpOptions.eventRequestHandler; - } else { - customEventRequestHandler = new BrowserRequestHandler({ - logger, - timeout:odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS - }); - } - - let eventManager: IOdpEventManager; - - if (odpOptions?.eventManager) { - eventManager = odpOptions.eventManager; - } else { - eventManager = new BrowserOdpEventManager({ - odpConfig, - apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, logger), - logger: logger, - clientEngine, - clientVersion, - flushInterval: odpOptions?.eventFlushInterval, - batchSize: odpOptions?.eventBatchSize, - queueSize: odpOptions?.eventQueueSize, - userAgentParser: odpOptions?.userAgentParser, - }); - } - - return new BrowserOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - }); - } - - /** - * @override - * accesses or creates new VUID from Browser cache - */ - protected async initializeVuid(): Promise<void> { - const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); - this.vuid = vuidManager.vuid; - } - - /** - * @override - * - Still identifies a user via the ODP Event Manager - * - Additionally, also passes VUID to help identify client-side users - * @param fsUserId Unique identifier of a target user. - */ - identifyUser(fsUserId?: string, vuid?: string): void { - if (fsUserId && VuidManager.isVuid(fsUserId)) { - super.identifyUser(undefined, fsUserId); - return; - } - - if (fsUserId && vuid && VuidManager.isVuid(vuid)) { - super.identifyUser(fsUserId, vuid); - return; - } - - super.identifyUser(fsUserId, vuid || this.vuid); - } - - /** - * @override - * - Sends an event to the ODP Server via the ODP Events API - * - Intercepts identifiers and injects VUID before sending event - * - Identifiers must contain at least one key-value pair - * @param {OdpEvent} odpEvent > ODP Event to send to event manager - */ - sendEvent({ type, action, identifiers, data }: OdpEvent): void { - const identifiersWithVuid = new Map<string, string>(identifiers); - - if (!identifiers.has(ODP_USER_KEY.VUID)) { - if (this.vuid) { - identifiersWithVuid.set(ODP_USER_KEY.VUID, this.vuid); - } else { - throw new Error(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING); - } - } - - super.sendEvent({ type, action, identifiers: identifiersWithVuid, data }); - } - - isVuidEnabled(): boolean { - return true; - } - - getVuid(): string | undefined { - return this.vuid; - } -} diff --git a/lib/odp/odp_manager.node.ts b/lib/odp/odp_manager.node.ts deleted file mode 100644 index 648e27751..000000000 --- a/lib/odp/odp_manager.node.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright 2023-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; - -import { ServerLRUCache } from '../utils/lru_cache/server_lru_cache'; - -import { getLogger, LogHandler, LogLevel } from '../modules/logging'; -import { - NODE_CLIENT_ENGINE, - CLIENT_VERSION, - REQUEST_TIMEOUT_ODP_EVENTS_MS, - REQUEST_TIMEOUT_ODP_SEGMENTS_MS, -} from '../utils/enums'; - -import { OdpManager } from './odp_manager'; -import { IOdpEventManager, OdpOptions } from '../shared_types'; -import { NodeOdpEventApiManager } from './event_manager/event_api_manager.node'; -import { NodeOdpEventManager } from './event_manager/event_manager.node'; -import { IOdpSegmentManager, OdpSegmentManager } from './segment_manager/odp_segment_manager'; -import { OdpSegmentApiManager } from './segment_manager/odp_segment_api_manager'; -import { OdpConfig, OdpIntegrationConfig } from './odp_config'; - -interface NodeOdpManagerConfig { - clientEngine?: string, - clientVersion?: string, - logger?: LogHandler; - odpOptions?: OdpOptions; - odpIntegrationConfig?: OdpIntegrationConfig; -} - -/** - * Server-side Node Plugin for ODP Manager. - * Note: As this is still a work-in-progress. Please avoid using the Node ODP Manager. - */ -export class NodeOdpManager extends OdpManager { - constructor(options: { - odpIntegrationConfig?: OdpIntegrationConfig; - segmentManager: IOdpSegmentManager; - eventManager: IOdpEventManager; - logger: LogHandler; - }) { - super(options); - } - - static createInstance({ - logger, odpOptions, odpIntegrationConfig, clientEngine, clientVersion - }: NodeOdpManagerConfig): NodeOdpManager { - logger = logger || getLogger(); - - clientEngine = clientEngine || NODE_CLIENT_ENGINE; - clientVersion = clientVersion || CLIENT_VERSION; - - let odpConfig : OdpConfig | undefined = undefined; - if (odpIntegrationConfig?.integrated) { - odpConfig = odpIntegrationConfig.odpConfig; - } - - let customSegmentRequestHandler; - - if (odpOptions?.segmentsRequestHandler) { - customSegmentRequestHandler = odpOptions.segmentsRequestHandler; - } else { - customSegmentRequestHandler = new NodeRequestHandler({ - logger, - timeout: odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS - }); - } - - let segmentManager: IOdpSegmentManager; - - if (odpOptions?.segmentManager) { - segmentManager = odpOptions.segmentManager; - } else { - segmentManager = new OdpSegmentManager( - odpOptions?.segmentsCache || - new ServerLRUCache<string, string[]>({ - maxSize: odpOptions?.segmentsCacheSize, - timeout: odpOptions?.segmentsCacheTimeout, - }), - new OdpSegmentApiManager(customSegmentRequestHandler, logger), - logger, - odpConfig - ); - } - - let customEventRequestHandler; - - if (odpOptions?.eventRequestHandler) { - customEventRequestHandler = odpOptions.eventRequestHandler; - } else { - customEventRequestHandler = new NodeRequestHandler({ - logger, - timeout: odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS - }); - } - - let eventManager: IOdpEventManager; - - if (odpOptions?.eventManager) { - eventManager = odpOptions.eventManager; - } else { - eventManager = new NodeOdpEventManager({ - odpConfig, - apiManager: new NodeOdpEventApiManager(customEventRequestHandler, logger), - logger: logger, - clientEngine, - clientVersion, - flushInterval: odpOptions?.eventFlushInterval, - batchSize: odpOptions?.eventBatchSize, - queueSize: odpOptions?.eventQueueSize, - userAgentParser: odpOptions?.userAgentParser, - }); - } - - return new NodeOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - }); - } - - public isVuidEnabled(): boolean { - return false; - } - - public getVuid(): string | undefined { - return undefined; - } -} diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts new file mode 100644 index 000000000..2464bc28b --- /dev/null +++ b/lib/odp/odp_manager.spec.ts @@ -0,0 +1,699 @@ +/** + * Copyright 2023-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, vi, expect } from 'vitest'; + + +import { DefaultOdpManager } from './odp_manager'; +import { ServiceState } from '../service'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { OdpConfig } from './odp_config'; +import { exhaustMicrotasks } from '../tests/testUtils'; +import { ODP_USER_KEY } from './constant'; +import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; +import { OdpEventManager } from './event_manager/odp_event_manager'; +import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; + +const keyA = 'key-a'; +const hostA = 'host-a'; +const pixelA = 'pixel-a'; +const segmentsA = ['a']; +const userA = 'fs-user-a'; + +const keyB = 'key-b'; +const hostB = 'host-b'; +const pixelB = 'pixel-b'; +const segmentsB = ['b']; +const userB = 'fs-user-b'; + +const config = new OdpConfig(keyA, hostA, pixelA, segmentsA); +const updatedConfig = new OdpConfig(keyB, hostB, pixelB, segmentsB); + +const getMockOdpEventManager = () => { + return { + start: vi.fn(), + stop: vi.fn(), + onRunning: vi.fn(), + onTerminated: vi.fn(), + getState: vi.fn(), + updateConfig: vi.fn(), + sendEvent: vi.fn(), + }; +}; + +const getMockOdpSegmentManager = () => { + return { + fetchQualifiedSegments: vi.fn(), + updateConfig: vi.fn(), + }; +}; + +describe('DefaultOdpManager', () => { + it('should be in new state on construction', () => { + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager: getMockOdpEventManager(), + }); + + expect(odpManager.getState()).toEqual(ServiceState.New); + }); + + it('should be in starting state after start is called', () => { + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + + expect(odpManager.getState()).toEqual(ServiceState.Starting); + }); + + it('should start eventManager after start is called', () => { + const eventManager = getMockOdpEventManager(); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(eventManager.start).toHaveBeenCalled(); + }); + + it('should stay in starting state if updateConfig is called but eventManager is still not running', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(resolvablePromise<void>().promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + }); + + it('should stay in starting state if eventManager is running but config is not yet available', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + }); + + it('should go to running state and resolve onRunning() if updateConfig is called and eventManager is running', async () => { + const eventManager = getMockOdpEventManager(); + const eventManagerPromise = resolvablePromise<void>(); + eventManager.onRunning.mockReturnValue(eventManagerPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await exhaustMicrotasks(); + + expect(odpManager.getState()).toEqual(ServiceState.Starting); + eventManagerPromise.resolve(); + + await expect(odpManager.onRunning()).resolves.not.toThrow(); + expect(odpManager.getState()).toEqual(ServiceState.Running); + }); + + it('should go to failed state and reject onRunning(), onTerminated() if updateConfig is called and eventManager fails to start', async () => { + const eventManager = getMockOdpEventManager(); + const eventManagerPromise = resolvablePromise<void>(); + eventManager.onRunning.mockReturnValue(eventManagerPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await exhaustMicrotasks(); + + expect(odpManager.getState()).toEqual(ServiceState.Starting); + eventManagerPromise.reject(new Error('Failed to start')); + + await expect(odpManager.onRunning()).rejects.toThrow(); + await expect(odpManager.onTerminated()).rejects.toThrow(); + expect(odpManager.getState()).toEqual(ServiceState.Failed); + }); + + it('should go to failed state and reject onRunning(), onTerminated() if eventManager fails to start before updateSettings()', async () => { + const eventManager = getMockOdpEventManager(); + const eventManagerPromise = resolvablePromise<void>(); + eventManager.onRunning.mockReturnValue(eventManagerPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + eventManagerPromise.reject(new Error('Failed to start')); + + await expect(odpManager.onRunning()).rejects.toThrow(); + await expect(odpManager.onTerminated()).rejects.toThrow(); + expect(odpManager.getState()).toEqual(ServiceState.Failed); + }); + + it('should pass the changed config to eventManager and segmentManager', async () => { + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + expect(eventManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + expect(segmentManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + + odpManager.updateConfig({ integrated: true, odpConfig: updatedConfig }); + + expect(eventManager.updateConfig).toHaveBeenNthCalledWith(2, { integrated: true, odpConfig: updatedConfig }); + expect(segmentManager.updateConfig).toHaveBeenNthCalledWith(2, { integrated: true, odpConfig: updatedConfig }); + expect(eventManager.updateConfig).toHaveBeenCalledTimes(2); + expect(segmentManager.updateConfig).toHaveBeenCalledTimes(2); + }); + + it('should not call eventManager and segmentManager updateConfig if config does not change', async () => { + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + expect(eventManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + expect(segmentManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + + odpManager.updateConfig({ integrated: true, odpConfig: JSON.parse(JSON.stringify(config)) }); + + expect(eventManager.updateConfig).toHaveBeenCalledTimes(1); + expect(segmentManager.updateConfig).toHaveBeenCalledTimes(1); + }); + + it('fetches qualified segments correctly for both fs_user_id and vuid from segmentManager', async () => { + const segmentManager = getMockOdpSegmentManager(); + segmentManager.fetchQualifiedSegments.mockImplementation((key: ODP_USER_KEY) => { + if (key === ODP_USER_KEY.FS_USER_ID) { + return Promise.resolve(['fs1', 'fs2']); + } + return Promise.resolve(['vuid1', 'vuid2']); + }); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const fsSegments = await odpManager.fetchQualifiedSegments(userA); + expect(fsSegments).toEqual(['fs1', 'fs2']); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(1, ODP_USER_KEY.FS_USER_ID, userA, []); + + const vuidSegments = await odpManager.fetchQualifiedSegments('vuid_abcd'); + expect(vuidSegments).toEqual(['vuid1', 'vuid2']); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(2, ODP_USER_KEY.VUID, 'vuid_abcd', []); + }); + + it('returns null from fetchQualifiedSegments if segmentManger returns null', async () => { + const segmentManager = getMockOdpSegmentManager(); + segmentManager.fetchQualifiedSegments.mockResolvedValue(null); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const fsSegments = await odpManager.fetchQualifiedSegments(userA); + expect(fsSegments).toBeNull(); + + const vuidSegments = await odpManager.fetchQualifiedSegments('vuid_abcd'); + expect(vuidSegments).toBeNull(); + }); + + it('passes options to segmentManager correctly', async () => { + const segmentManager = getMockOdpSegmentManager(); + segmentManager.fetchQualifiedSegments.mockResolvedValue(null); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const options = [OptimizelySegmentOption.IGNORE_CACHE, OptimizelySegmentOption.RESET_CACHE]; + await odpManager.fetchQualifiedSegments(userA, options); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(1, ODP_USER_KEY.FS_USER_ID, userA, options); + + await odpManager.fetchQualifiedSegments('vuid_abcd', options); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(2, ODP_USER_KEY.VUID, 'vuid_abcd', options); + + await odpManager.fetchQualifiedSegments(userA, [OptimizelySegmentOption.IGNORE_CACHE]); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith( + 3, ODP_USER_KEY.FS_USER_ID, userA, [OptimizelySegmentOption.IGNORE_CACHE]); + + await odpManager.fetchQualifiedSegments('vuid_abcd', []); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(4, ODP_USER_KEY.VUID, 'vuid_abcd', []); + }); + + it('sends a client_intialized event with the vuid after becoming ready if setVuid is called and odp is integrated', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.setVuid('vuid_123'); + + await exhaustMicrotasks(); + expect(eventManager.sendEvent).not.toHaveBeenCalled(); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + expect(mockSendEvents).toHaveBeenCalledOnce(); + + const { type, action, identifiers } = mockSendEvents.mock.calls[0][0]; + expect(type).toEqual('fullstack'); + expect(action).toEqual('client_initialized'); + expect(identifiers).toEqual(new Map([['vuid', 'vuid_123']])); + }); + + it('does not send a client_intialized event with the vuid after becoming ready if setVuid is called and odp is not integrated', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.setVuid('vuid_123'); + + await exhaustMicrotasks(); + expect(eventManager.sendEvent).not.toHaveBeenCalled(); + + odpManager.updateConfig({ integrated: false }); + await odpManager.onRunning(); + + await exhaustMicrotasks(); + expect(mockSendEvents).not.toHaveBeenCalled(); + }); + + it('includes the available vuid in events sent via sendEvent', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.setVuid('vuid_123'); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['email', 'a@b.com'], ['vuid', 'vuid_123']])); + }); + + it('does not override the vuid in events sent via sendEvent', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.setVuid('vuid_123'); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com'], ['vuid', 'vuid_456']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['email', 'a@b.com'], ['vuid', 'vuid_456']])); + }); + + it('augments the data with common data before sending the event', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { data } = mockSendEvents.mock.calls[0][0]; + expect(data.get('idempotence_id')).toBeDefined(); + expect(data.get('data_source_type')).toEqual('sdk'); + expect(data.get('data_source')).toEqual(JAVASCRIPT_CLIENT_ENGINE); + expect(data.get('data_source_version')).toEqual(CLIENT_VERSION); + expect(data.get('key1')).toEqual('value1'); + expect(data.get('key2')).toEqual('value2'); + }); + + it('uses the clientInfo provided by setClientInfo() when augmenting the data', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.setClientInfo('client', 'version'); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { data } = mockSendEvents.mock.calls[0][0]; + expect(data.get('data_source')).toEqual('client'); + expect(data.get('data_source_version')).toEqual('version'); + }); + + it('augments the data with user agent data before sending the event if userAgentParser is provided ', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + userAgentParser: { + parseUserAgentInfo: () => ({ + os: { name: 'os', version: '1.0' }, + device: { type: 'phone', model: 'model' }, + }), + }, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { data } = mockSendEvents.mock.calls[0][0]; + expect(data.get('os')).toEqual('os'); + expect(data.get('os_version')).toEqual('1.0'); + expect(data.get('device_type')).toEqual('phone'); + expect(data.get('model')).toEqual('model'); + }); + + it('sends identified event with both fs_user_id and vuid if both parameters are provided', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.identifyUser('user', 'vuid_a'); + expect(mockSendEvents).toHaveBeenCalledOnce(); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['fs_user_id', 'user'], ['vuid', 'vuid_a']])); + }); + + it('sends identified event when called with just fs_user_id in first parameter', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.identifyUser('user'); + expect(mockSendEvents).toHaveBeenCalledOnce(); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['fs_user_id', 'user']])); + }); + + it('sends identified event when called with just vuid in first parameter', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.identifyUser('vuid_a'); + expect(mockSendEvents).toHaveBeenCalledOnce(); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['vuid', 'vuid_a']])); + }); + + it('should reject onRunning() if stopped in new state', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.stop(); + + await expect(odpManager.onRunning()).rejects.toThrow(); + }); + + it('should reject onRunning() if stopped in starting state', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.stop(); + await expect(odpManager.onRunning()).rejects.toThrow(); + }); + + it('should go to stopping state and wait for eventManager to stop if stop is called', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(resolvablePromise().promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.stop(); + + const terminatedHandler = vi.fn(); + odpManager.onTerminated().then(terminatedHandler); + + expect(odpManager.getState()).toEqual(ServiceState.Stopping); + await exhaustMicrotasks(); + expect(terminatedHandler).not.toHaveBeenCalled(); + }); + + it('should stop eventManager if stop is called', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + + odpManager.stop(); + expect(eventManager.stop).toHaveBeenCalled(); + }); + + it('should resolve onTerminated after eventManager stops successfully', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + const eventManagerTerminatedPromise = resolvablePromise<void>(); + eventManager.onTerminated.mockReturnValue(eventManagerTerminatedPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.stop(); + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Stopping); + + eventManagerTerminatedPromise.resolve(); + await expect(odpManager.onTerminated()).resolves.not.toThrow(); + }); + + it('should reject onTerminated after eventManager fails to stop correctly', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + const eventManagerTerminatedPromise = resolvablePromise<void>(); + eventManager.onTerminated.mockReturnValue(eventManagerTerminatedPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.stop(); + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Stopping); + + eventManagerTerminatedPromise.reject(new Error('Failed to stop')); + await expect(odpManager.onTerminated()).rejects.toThrow(); + }); +}); + diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index df2bbc394..560e445a4 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -14,190 +14,157 @@ * limitations under the License. */ -import { LogHandler, LogLevel } from '../modules/logging'; -import { ERROR_MESSAGES, ODP_USER_KEY } from '../utils/enums'; - -import { VuidManager } from '../plugins/vuid_manager'; +import { v4 as uuidV4} from 'uuid'; +import { LoggerFacade } from '../modules/logging'; import { OdpIntegrationConfig, odpIntegrationsAreEqual } from './odp_config'; -import { IOdpEventManager } from './event_manager/odp_event_manager'; -import { IOdpSegmentManager } from './segment_manager/odp_segment_manager'; +import { OdpEventManager } from './event_manager/odp_event_manager'; +import { OdpSegmentManager } from './segment_manager/odp_segment_manager'; import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; -import { invalidOdpDataFound } from './odp_utils'; import { OdpEvent } from './event_manager/odp_event'; import { resolvablePromise, ResolvablePromise } from '../utils/promise/resolvablePromise'; - -/** - * Manager for handling internal all business logic related to - * Optimizely Data Platform (ODP) / Advanced Audience Targeting (AAT) - */ -export interface IOdpManager { - onReady(): Promise<unknown>; - - isReady(): boolean; - - updateSettings(odpIntegrationConfig: OdpIntegrationConfig): boolean; - - stop(): void; - +import { BaseService, Service, ServiceState } from '../service'; +import { UserAgentParser } from './ua_parser/user_agent_parser'; +import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; +import { ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION, ODP_USER_KEY } from './constant'; +import { isVuid } from '../vuid/vuid'; +import { Maybe } from '../utils/type'; + +export interface OdpManager extends Service { + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean; fetchQualifiedSegments(userId: string, options?: Array<OptimizelySegmentOption>): Promise<string[] | null>; - - identifyUser(userId?: string, vuid?: string): void; - - sendEvent({ type, action, identifiers, data }: OdpEvent): void; - - isVuidEnabled(): boolean; - - getVuid(): string | undefined; -} - -export enum Status { - Running, - Stopped, + identifyUser(userId: string, vuid?: string): void; + sendEvent(event: OdpEvent): void; + setClientInfo(clientEngine: string, clientVersion: string): void; + setVuid(vuid: string): void; } -/** - * Orchestrates segments manager, event manager, and ODP configuration - */ -export abstract class OdpManager implements IOdpManager { - /** - * Promise that returns when the OdpManager is finished initializing - */ - private initPromise: Promise<unknown>; - private ready = false; +export type OdpManagerConfig = { + segmentManager: OdpSegmentManager; + eventManager: OdpEventManager; + logger?: LoggerFacade; + userAgentParser?: UserAgentParser; +}; - /** - * Promise that resolves when odpConfig becomes available - */ +export class DefaultOdpManager extends BaseService implements OdpManager { private configPromise: ResolvablePromise<void>; - - status: Status = Status.Stopped; - - /** - * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. - * It fetches all qualified segments for the given user context and manages the segments cache for all user contexts. - */ - private segmentManager: IOdpSegmentManager; - - /** - * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. - * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. - */ - private eventManager: IOdpEventManager; - - /** - * Handler for recording execution logs - * @protected - */ - protected logger: LogHandler; - - /** - * ODP configuration settings for identifying the target API and segments - */ - odpIntegrationConfig?: OdpIntegrationConfig; - - // TODO: Consider accepting logger as a parameter and initializing it in constructor instead - constructor({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - }: { - odpIntegrationConfig?: OdpIntegrationConfig; - segmentManager: IOdpSegmentManager; - eventManager: IOdpEventManager; - logger: LogHandler; - }) { - this.segmentManager = segmentManager; - this.eventManager = eventManager; - this.logger = logger; + private segmentManager: OdpSegmentManager; + private eventManager: OdpEventManager; + private odpIntegrationConfig?: OdpIntegrationConfig; + private vuid?: string; + private clientEngine = JAVASCRIPT_CLIENT_ENGINE; + private clientVersion = CLIENT_VERSION; + private userAgentData?: Map<string, unknown>; + + constructor(config: OdpManagerConfig) { + super(); + this.segmentManager = config.segmentManager; + this.eventManager = config.eventManager; + this.logger = config.logger; this.configPromise = resolvablePromise(); - const readinessDependencies: PromiseLike<unknown>[] = [this.configPromise]; + if (config.userAgentParser) { + const { os, device } = config.userAgentParser.parseUserAgentInfo(); - if (this.isVuidEnabled()) { - readinessDependencies.push(this.initializeVuid()); - } - - this.initPromise = Promise.all(readinessDependencies); - - this.onReady().then(() => { - this.ready = true; - if (this.isVuidEnabled() && this.status === Status.Running) { - this.registerVuid(); - } - }); + const userAgentInfo: Record<string, unknown> = { + 'os': os.name, + 'os_version': os.version, + 'device_type': device.type, + 'model': device.model, + }; - if (odpIntegrationConfig) { - this.updateSettings(odpIntegrationConfig); + this.userAgentData = new Map<string, unknown>( + Object.entries(userAgentInfo).filter(([_, value]) => value != null && value != undefined) + ); } } - public getStatus(): Status { - return this.status; + setClientInfo(clientEngine: string, clientVersion: string): void { + this.clientEngine = clientEngine; + this.clientVersion = clientVersion; } - async start(): Promise<void> { - if (this.status === Status.Running) { + start(): void { + if (!this.isNew()) { return; } - if (!this.odpIntegrationConfig) { - return Promise.reject(new Error('cannot start without ODP config')); - } + this.state = ServiceState.Starting; - if (!this.odpIntegrationConfig.integrated) { - return Promise.reject(new Error('start() called when ODP is not integrated')); - } - - this.status = Status.Running; - this.segmentManager.updateSettings(this.odpIntegrationConfig.odpConfig); - this.eventManager.updateSettings(this.odpIntegrationConfig.odpConfig); this.eventManager.start(); - return Promise.resolve(); + + const startDependencies = [ + this.configPromise, + this.eventManager.onRunning(), + ]; + + Promise.all(startDependencies) + .then(() => { + this.handleStartSuccess(); + }).catch((err) => { + this.handleStartFailure(err); + }); } - async stop(): Promise<void> { - if (this.status === Status.Stopped) { + private handleStartSuccess() { + if (this.isDone()) { return; } - this.status = Status.Stopped; - await this.eventManager.stop(); + this.state = ServiceState.Running; + this.startPromise.resolve(); } - onReady(): Promise<unknown> { - return this.initPromise; - } + private handleStartFailure(error: Error) { + if (this.isDone()) { + return; + } - isReady(): boolean { - return this.ready; + this.state = ServiceState.Failed; + this.startPromise.reject(error); + this.stopPromise.reject(error); } - /** - * Provides a method to update ODP Manager's ODP Config - */ - updateSettings(odpIntegrationConfig: OdpIntegrationConfig): boolean { - this.configPromise.resolve(); + stop(): void { + if (this.isDone()) { + return; + } + + if (!this.isRunning()) { + this.startPromise.reject(new Error('odp manager stopped before running')); + } + this.state = ServiceState.Stopping; + this.eventManager.stop(); + + this.eventManager.onTerminated().then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + }).catch((err) => { + this.state = ServiceState.Failed; + this.stopPromise.reject(err); + }); + } + + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean { // do nothing if config did not change if (this.odpIntegrationConfig && odpIntegrationsAreEqual(this.odpIntegrationConfig, odpIntegrationConfig)) { return false; } + if (this.isDone()) { + return false; + } + this.odpIntegrationConfig = odpIntegrationConfig; - if (odpIntegrationConfig.integrated) { - // already running, just propagate updated config to children; - if (this.status === Status.Running) { - this.segmentManager.updateSettings(odpIntegrationConfig.odpConfig); - this.eventManager.updateSettings(odpIntegrationConfig.odpConfig); - } else { - this.start(); - } - } else { - this.stop(); + if (this.isStarting()) { + this.configPromise.resolve(); } + + this.segmentManager.updateConfig(odpIntegrationConfig) + this.eventManager.updateConfig(odpIntegrationConfig); + return true; } @@ -209,114 +176,64 @@ export abstract class OdpManager implements IOdpManager { * @returns {Promise<string[] | null>} A promise holding either a list of qualified segments or null. */ async fetchQualifiedSegments(userId: string, options: Array<OptimizelySegmentOption> = []): Promise<string[] | null> { - if (!this.odpIntegrationConfig) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); - return null; - } - - if (!this.odpIntegrationConfig.integrated) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); - return null; - } - - if (VuidManager.isVuid(userId)) { + if (isVuid(userId)) { return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, userId, options); } return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); } - /** - * Identifies a user via the ODP Event Manager - * @param {string} userId (Optional) Custom unique identifier of a target user. - * @param {string} vuid (Optional) Secondary unique identifier of a target user, primarily used by client SDKs. - * @returns - */ - identifyUser(userId?: string, vuid?: string): void { - if (!this.odpIntegrationConfig) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); - return; - } - - if (!this.odpIntegrationConfig.integrated) { - this.logger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_INTEGRATED); - return; - } - - if (userId && VuidManager.isVuid(userId)) { - this.eventManager.identifyUser(undefined, userId); - return; - } - - this.eventManager.identifyUser(userId, vuid); - } - - /** - * Sends an event to the ODP Server via the ODP Events API - * @param {OdpEvent} > ODP Event to send to event manager - */ - sendEvent({ type, action, identifiers, data }: OdpEvent): void { - let mType = type; + identifyUser(userId: string, vuid?: string): void { + const identifiers = new Map<string, string>(); + + let finalUserId: Maybe<string> = userId; + let finalVuid: Maybe<string> = vuid; - if (typeof mType !== 'string' || mType === '') { - mType = 'fullstack'; + if (!vuid && isVuid(userId)) { + finalVuid = userId; + finalUserId = undefined; } - if (!this.odpIntegrationConfig) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); - return; + if (finalVuid) { + identifiers.set(ODP_USER_KEY.VUID, finalVuid); } - if (!this.odpIntegrationConfig.integrated) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); - return; + if (finalUserId) { + identifiers.set(ODP_USER_KEY.FS_USER_ID, finalUserId); } - if (invalidOdpDataFound(data)) { - throw new Error(ERROR_MESSAGES.ODP_INVALID_DATA); - } + const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.IDENTIFIED, identifiers); + this.sendEvent(event); + } - if (typeof action !== 'string' || action === '') { - throw new Error('ODP action is not valid (cannot be empty).'); + sendEvent(event: OdpEvent): void { + if (!event.identifiers.has(ODP_USER_KEY.VUID) && this.vuid) { + event.identifiers.set(ODP_USER_KEY.VUID, this.vuid); } - this.eventManager.sendEvent(new OdpEvent(mType, action, identifiers, data)); + event.data = this.augmentCommonData(event.data); + this.eventManager.sendEvent(event); } - /** - * Identifies if the VUID feature is enabled - */ - abstract isVuidEnabled(): boolean; - - /** - * Returns VUID value if it exists - */ - abstract getVuid(): string | undefined; + private augmentCommonData(sourceData: Map<string, unknown>): Map<string, unknown> { + const data = new Map<string, unknown>(this.userAgentData); + + data.set('idempotence_id', uuidV4()); + data.set('data_source_type', 'sdk'); + data.set('data_source', this.clientEngine); + data.set('data_source_version', this.clientVersion); - protected initializeVuid(): Promise<void> { - return Promise.resolve(); + sourceData.forEach((value, key) => data.set(key, value)); + return data; } - private registerVuid() { - if (!this.odpIntegrationConfig) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); - return; - } - - if (!this.odpIntegrationConfig.integrated) { - this.logger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_INTEGRATED); - return; - } - - const vuid = this.getVuid(); - if (!vuid) { - return; - } - - try { - this.eventManager.registerVuid(vuid); - } catch (e) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED); - } + setVuid(vuid: string): void { + this.vuid = vuid; + this.onRunning().then(() => { + if (this.odpIntegrationConfig?.integrated) { + const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED); + this.sendEvent(event); + } + }); } } diff --git a/lib/odp/odp_manager_factory.browser.spec.ts b/lib/odp/odp_manager_factory.browser.spec.ts new file mode 100644 index 000000000..333856743 --- /dev/null +++ b/lib/odp/odp_manager_factory.browser.spec.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('../utils/http_request_handler/browser_request_handler', () => { + return { BrowserRequestHandler: vi.fn() }; +}); + +vi.mock('./odp_manager_factory', () => { + return { getOdpManager: vi.fn().mockImplementation(() => ({})) }; +}); + + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { BROWSER_DEFAULT_API_TIMEOUT, createOdpManager } from './odp_manager_factory.browser'; +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; + +describe('createOdpManager', () => { + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const mockGetOdpManager = vi.mocked(getOdpManager); + + beforeEach(() => { + MockBrowserRequestHandler.mockClear(); + mockGetOdpManager.mockClear(); + }); + + it('should use BrowserRequestHandler with the provided timeout as the segment request handler', () => { + const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(3456); + }); + + it('should use BrowserRequestHandler with the browser default timeout as the segment request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(BROWSER_DEFAULT_API_TIMEOUT); + }); + + it('should use BrowserRequestHandler with the provided timeout as the event request handler', () => { + const odpManager = createOdpManager({ eventApiTimeout: 2345 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(2345); + }); + + it('should use BrowserRequestHandler with the browser default timeout as the event request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(BROWSER_DEFAULT_API_TIMEOUT); + }); + + it('should use batchSize 1 if batchSize is not provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(1); + }); + + it('should use batchSize 1 event if some other batchSize value is provided', () => { + const odpManager = createOdpManager({ eventBatchSize: 99 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(1); + }); + + it('uses the pixel api request generator', () => { + const odpManager = createOdpManager({ }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestGenerator).toBe(pixelApiRequestGenerator); + }); + + it('uses the passed options for relevant fields', () => { + const options: OdpManagerOptions = { + segmentsCache: {} as any, + segmentsCacheSize: 11, + segmentsCacheTimeout: 2025, + segmentManager: {} as any, + eventFlushInterval: 2222, + eventManager: {} as any, + userAgentParser: {} as any, + }; + const odpManager = createOdpManager(options); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + expect(mockGetOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + }); +}); diff --git a/lib/odp/odp_manager_factory.browser.ts b/lib/odp/odp_manager_factory.browser.ts new file mode 100644 index 000000000..481252278 --- /dev/null +++ b/lib/odp/odp_manager_factory.browser.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { OdpManager } from './odp_manager'; +import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; + +export const BROWSER_DEFAULT_API_TIMEOUT = 10_000; + +export const createOdpManager = (options: OdpManagerOptions): OdpManager => { + const segmentRequestHandler = new BrowserRequestHandler({ + timeout: options.segmentsApiTimeout || BROWSER_DEFAULT_API_TIMEOUT, + }); + + const eventRequestHandler = new BrowserRequestHandler({ + timeout: options.eventApiTimeout || BROWSER_DEFAULT_API_TIMEOUT, + }); + + return getOdpManager({ + ...options, + eventBatchSize: 1, + segmentRequestHandler, + eventRequestHandler, + eventRequestGenerator: pixelApiRequestGenerator, + }); +}; diff --git a/lib/odp/odp_manager_factory.node.spec.ts b/lib/odp/odp_manager_factory.node.spec.ts new file mode 100644 index 000000000..b63850180 --- /dev/null +++ b/lib/odp/odp_manager_factory.node.spec.ts @@ -0,0 +1,125 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('../utils/http_request_handler/node_request_handler', () => { + return { NodeRequestHandler: vi.fn() }; +}); + +vi.mock('./odp_manager_factory', () => { + return { getOdpManager: vi.fn().mockImplementation(() => ({})) }; +}); + + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { NODE_DEFAULT_API_TIMEOUT, NODE_DEFAULT_BATCH_SIZE, NODE_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.node'; +import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; + +describe('createOdpManager', () => { + const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); + const mockGetOdpManager = vi.mocked(getOdpManager); + + beforeEach(() => { + MockNodeRequestHandler.mockClear(); + mockGetOdpManager.mockClear(); + }); + + it('should use NodeRequestHandler with the provided timeout as the segment request handler', () => { + const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockNodeRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(3456); + }); + + it('should use NodeRequestHandler with the node default timeout as the segment request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockNodeRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(NODE_DEFAULT_API_TIMEOUT); + }); + + it('should use NodeRequestHandler with the provided timeout as the event request handler', () => { + const odpManager = createOdpManager({ eventApiTimeout: 2345 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockNodeRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(2345); + }); + + it('should use NodeRequestHandler with the node default timeout as the event request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockNodeRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(NODE_DEFAULT_API_TIMEOUT); + }); + + it('uses the event api request generator', () => { + const odpManager = createOdpManager({ }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestGenerator).toBe(eventApiRequestGenerator); + }); + + it('should use the provided eventBatchSize', () => { + const odpManager = createOdpManager({ eventBatchSize: 99 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(99); + }); + + it('should use the node default eventBatchSize if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(NODE_DEFAULT_BATCH_SIZE); + }); + + it('should use the provided eventFlushInterval', () => { + const odpManager = createOdpManager({ eventFlushInterval: 9999 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(9999); + }); + + it('should use the node default eventFlushInterval if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(NODE_DEFAULT_FLUSH_INTERVAL); + }); + + it('uses the passed options for relevant fields', () => { + const options: OdpManagerOptions = { + segmentsCache: {} as any, + segmentsCacheSize: 11, + segmentsCacheTimeout: 2025, + segmentManager: {} as any, + eventManager: {} as any, + userAgentParser: {} as any, + }; + const odpManager = createOdpManager(options); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + expect(mockGetOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + }); +}); diff --git a/lib/odp/odp_manager_factory.node.ts b/lib/odp/odp_manager_factory.node.ts new file mode 100644 index 000000000..3d449fd3b --- /dev/null +++ b/lib/odp/odp_manager_factory.node.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { OdpManager } from './odp_manager'; +import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; + +export const NODE_DEFAULT_API_TIMEOUT = 10_000; +export const NODE_DEFAULT_BATCH_SIZE = 10; +export const NODE_DEFAULT_FLUSH_INTERVAL = 1000; + +export const createOdpManager = (options: OdpManagerOptions): OdpManager => { + const segmentRequestHandler = new NodeRequestHandler({ + timeout: options.segmentsApiTimeout || NODE_DEFAULT_API_TIMEOUT, + }); + + const eventRequestHandler = new NodeRequestHandler({ + timeout: options.eventApiTimeout || NODE_DEFAULT_API_TIMEOUT, + }); + + return getOdpManager({ + ...options, + segmentRequestHandler, + eventRequestHandler, + eventBatchSize: options.eventBatchSize || NODE_DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || NODE_DEFAULT_FLUSH_INTERVAL, + eventRequestGenerator: eventApiRequestGenerator, + }); +}; diff --git a/lib/odp/odp_manager_factory.react_native.spec.ts b/lib/odp/odp_manager_factory.react_native.spec.ts new file mode 100644 index 000000000..604a71bc7 --- /dev/null +++ b/lib/odp/odp_manager_factory.react_native.spec.ts @@ -0,0 +1,125 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('../utils/http_request_handler/browser_request_handler', () => { + return { BrowserRequestHandler: vi.fn() }; +}); + +vi.mock('./odp_manager_factory', () => { + return { getOdpManager: vi.fn().mockImplementation(() => ({})) }; +}); + + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { RN_DEFAULT_API_TIMEOUT, RN_DEFAULT_BATCH_SIZE, RN_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.react_native'; +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler' +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; + +describe('createOdpManager', () => { + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const mockGetOdpManager = vi.mocked(getOdpManager); + + beforeEach(() => { + MockBrowserRequestHandler.mockClear(); + mockGetOdpManager.mockClear(); + }); + + it('should use BrowserRequestHandler with the provided timeout as the segment request handler', () => { + const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(3456); + }); + + it('should use BrowserRequestHandler with the node default timeout as the segment request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(RN_DEFAULT_API_TIMEOUT); + }); + + it('should use BrowserRequestHandler with the provided timeout as the event request handler', () => { + const odpManager = createOdpManager({ eventApiTimeout: 2345 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(2345); + }); + + it('should use BrowserRequestHandler with the node default timeout as the event request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(RN_DEFAULT_API_TIMEOUT); + }); + + it('uses the event api request generator', () => { + const odpManager = createOdpManager({ }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOdpManager.mock.calls[0][0]; + expect(eventRequestGenerator).toBe(eventApiRequestGenerator); + }); + + it('should use the provided eventBatchSize', () => { + const odpManager = createOdpManager({ eventBatchSize: 99 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(99); + }); + + it('should use the react_native default eventBatchSize if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(RN_DEFAULT_BATCH_SIZE); + }); + + it('should use the provided eventFlushInterval', () => { + const odpManager = createOdpManager({ eventFlushInterval: 9999 }); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(9999); + }); + + it('should use the react_native default eventFlushInterval if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(RN_DEFAULT_FLUSH_INTERVAL); + }); + + it('uses the passed options for relevant fields', () => { + const options: OdpManagerOptions = { + segmentsCache: {} as any, + segmentsCacheSize: 11, + segmentsCacheTimeout: 2025, + segmentManager: {} as any, + eventManager: {} as any, + userAgentParser: {} as any, + }; + const odpManager = createOdpManager(options); + expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); + expect(mockGetOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + }); +}); diff --git a/lib/odp/odp_manager_factory.react_native.ts b/lib/odp/odp_manager_factory.react_native.ts new file mode 100644 index 000000000..c63982430 --- /dev/null +++ b/lib/odp/odp_manager_factory.react_native.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { OdpManager } from './odp_manager'; +import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; + +export const RN_DEFAULT_API_TIMEOUT = 10_000; +export const RN_DEFAULT_BATCH_SIZE = 10; +export const RN_DEFAULT_FLUSH_INTERVAL = 1000; + +export const createOdpManager = (options: OdpManagerOptions): OdpManager => { + const segmentRequestHandler = new BrowserRequestHandler({ + timeout: options.segmentsApiTimeout || RN_DEFAULT_API_TIMEOUT, + }); + + const eventRequestHandler = new BrowserRequestHandler({ + timeout: options.eventApiTimeout || RN_DEFAULT_API_TIMEOUT, + }); + + return getOdpManager({ + ...options, + segmentRequestHandler, + eventRequestHandler, + eventBatchSize: options.eventBatchSize || RN_DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || RN_DEFAULT_FLUSH_INTERVAL, + eventRequestGenerator: eventApiRequestGenerator, + }); +}; diff --git a/lib/odp/odp_manager_factory.spec.ts b/lib/odp/odp_manager_factory.spec.ts new file mode 100644 index 000000000..94aa565e5 --- /dev/null +++ b/lib/odp/odp_manager_factory.spec.ts @@ -0,0 +1,405 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('./odp_manager', () => { + return { + DefaultOdpManager: vi.fn(), + }; +}); + +vi.mock('./segment_manager/odp_segment_manager', () => { + return { + DefaultOdpSegmentManager: vi.fn(), + }; +}); + +vi.mock('./segment_manager/odp_segment_api_manager', () => { + return { + DefaultOdpSegmentApiManager: vi.fn(), + }; +}); + +vi.mock('../utils/cache/in_memory_lru_cache', () => { + return { + InMemoryLruCache: vi.fn(), + }; +}); + +vi.mock('./event_manager/odp_event_manager', () => { + return { + DefaultOdpEventManager: vi.fn(), + }; +}); + +vi.mock('./event_manager/odp_event_api_manager', () => { + return { + DefaultOdpEventApiManager: vi.fn(), + }; +}); + +vi.mock( '../utils/repeater/repeater', () => { + return { + IntervalRepeater: vi.fn(), + ExponentialBackoff: vi.fn(), + }; +}); + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { DefaultOdpManager } from './odp_manager'; +import { DEFAULT_CACHE_SIZE, DEFAULT_CACHE_TIMEOUT, DEFAULT_EVENT_BATCH_SIZE, DEFAULT_EVENT_MAX_BACKOFF, DEFAULT_EVENT_MAX_RETRIES, DEFAULT_EVENT_MIN_BACKOFF, getOdpManager } from './odp_manager_factory'; +import { getMockRequestHandler } from '../tests/mock/mock_request_handler'; +import { DefaultOdpSegmentManager } from './segment_manager/odp_segment_manager'; +import { DefaultOdpSegmentApiManager } from './segment_manager/odp_segment_api_manager'; +import { InMemoryLruCache } from '../utils/cache/in_memory_lru_cache'; +import { DefaultOdpEventManager } from './event_manager/odp_event_manager'; +import { DefaultOdpEventApiManager } from './event_manager/odp_event_api_manager'; +import { IntervalRepeater } from '../utils/repeater/repeater'; +import { ExponentialBackoff } from '../utils/repeater/repeater'; + +describe('getOdpManager', () => { + const MockDefaultOdpManager = vi.mocked(DefaultOdpManager); + const MockDefaultOdpSegmentManager = vi.mocked(DefaultOdpSegmentManager); + const MockDefaultOdpSegmentApiManager = vi.mocked(DefaultOdpSegmentApiManager); + const MockInMemoryLruCache = vi.mocked(InMemoryLruCache); + const MockDefaultOdpEventManager = vi.mocked(DefaultOdpEventManager); + const MockDefaultOdpEventApiManager = vi.mocked(DefaultOdpEventApiManager); + const MockIntervalRepeater = vi.mocked(IntervalRepeater); + const MockExponentialBackoff = vi.mocked(ExponentialBackoff); + + beforeEach(() => { + MockDefaultOdpManager.mockClear(); + MockDefaultOdpSegmentManager.mockClear(); + MockDefaultOdpSegmentApiManager.mockClear(); + MockInMemoryLruCache.mockClear(); + MockDefaultOdpEventManager.mockClear(); + MockDefaultOdpEventApiManager.mockClear(); + MockIntervalRepeater.mockClear(); + MockExponentialBackoff.mockClear(); + }); + + it('should use provided segment manager', () => { + const segmentManager = {} as any; + + const odpManager = getOdpManager({ + segmentManager, + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedSegmentManager).toBe(segmentManager); + }); + + describe('when no segment manager is provided', () => { + it('should create a default segment manager with default api manager using the passed eventRequestHandler', () => { + const segmentRequestHandler = getMockRequestHandler(); + const odpManager = getOdpManager({ + segmentRequestHandler, + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const apiManager = MockDefaultOdpSegmentManager.mock.calls[0][1]; + expect(Object.is(apiManager, MockDefaultOdpSegmentApiManager.mock.instances[0])).toBe(true); + const usedRequestHandler = MockDefaultOdpSegmentApiManager.mock.calls[0][0]; + expect(Object.is(usedRequestHandler, segmentRequestHandler)).toBe(true); + }); + + it('should create a default segment manager with the provided segment cache', () => { + const segmentsCache = {} as any; + + const odpManager = getOdpManager({ + segmentsCache, + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(segmentsCache); + }); + + describe('when no segment cache is provided', () => { + it('should use a InMemoryLruCache with the provided size', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCacheSize: 3141, + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][0]).toBe(3141); + }); + + it('should use a InMemoryLruCache with default size if no segmentCacheSize is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][0]).toBe(DEFAULT_CACHE_SIZE); + }); + + it('should use a InMemoryLruCache with the provided timeout', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCacheTimeout: 123456, + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][1]).toBe(123456); + }); + + it('should use a InMemoryLruCache with default timeout if no segmentsCacheTimeout is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][1]).toBe(DEFAULT_CACHE_TIMEOUT); + }); + }); + }); + + it('uses provided event manager', () => { + const eventManager = {} as any; + + const odpManager = getOdpManager({ + eventManager, + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(eventManager); + }); + + describe('when no event manager is provided', () => { + it('should use a default event manager with default api manager using the passed eventRequestHandler and eventRequestGenerator', () => { + const eventRequestHandler = getMockRequestHandler(); + const eventRequestGenerator = vi.fn(); + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler, + eventRequestGenerator, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const apiManager = MockDefaultOdpEventManager.mock.calls[0][0].apiManager; + expect(apiManager).toBe(MockDefaultOdpEventApiManager.mock.instances[0]); + const usedRequestHandler = MockDefaultOdpEventApiManager.mock.calls[0][0]; + expect(usedRequestHandler).toBe(eventRequestHandler); + const usedRequestGenerator = MockDefaultOdpEventApiManager.mock.calls[0][1]; + expect(usedRequestGenerator).toBe(eventRequestGenerator); + }); + + it('should use a default event manager with the provided event batch size', () => { + const eventBatchSize = 1234; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventBatchSize, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBatchSize = MockDefaultOdpEventManager.mock.calls[0][0].batchSize; + expect(usedBatchSize).toBe(eventBatchSize); + }); + + it('should use a default event manager with the default batch size if no eventBatchSize is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBatchSize = MockDefaultOdpEventManager.mock.calls[0][0].batchSize; + expect(usedBatchSize).toBe(DEFAULT_EVENT_BATCH_SIZE); + }); + + it('should use a default event manager with an interval repeater with the provided flush interval', () => { + const eventFlushInterval = 1234; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventFlushInterval, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedRepeater = MockDefaultOdpEventManager.mock.calls[0][0].repeater; + expect(usedRepeater).toBe(MockIntervalRepeater.mock.instances[0]); + const usedInterval = MockIntervalRepeater.mock.calls[0][0]; + expect(usedInterval).toBe(eventFlushInterval); + }); + + it('should use a default event manager with the provided max retries', () => { + const eventMaxRetries = 7; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventMaxRetries, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedMaxRetries = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.maxRetries; + expect(usedMaxRetries).toBe(eventMaxRetries); + }); + + it('should use a default event manager with the default max retries if no eventMaxRetries is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedMaxRetries = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.maxRetries; + expect(usedMaxRetries).toBe(DEFAULT_EVENT_MAX_RETRIES); + }); + + it('should use a default event manager with ExponentialBackoff with provided minBackoff', () => { + const eventMinBackoff = 1234; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventMinBackoff, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][0]).toBe(eventMinBackoff); + }); + + it('should use a default event manager with ExponentialBackoff with default min backoff if none provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][0]).toBe(DEFAULT_EVENT_MIN_BACKOFF); + }); + + it('should use a default event manager with ExponentialBackoff with provided maxBackoff', () => { + const eventMaxBackoff = 9999; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventMaxBackoff: eventMaxBackoff, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][1]).toBe(eventMaxBackoff); + }); + + it('should use a default event manager with ExponentialBackoff with default max backoff if none provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][1]).toBe(DEFAULT_EVENT_MAX_BACKOFF); + }); + }); + + it('should use the provided userAgentParser', () => { + const userAgentParser = {} as any; + + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + userAgentParser, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { userAgentParser: usedUserAgentParser } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedUserAgentParser).toBe(userAgentParser); + }); +}); diff --git a/lib/odp/odp_manager_factory.ts b/lib/odp/odp_manager_factory.ts new file mode 100644 index 000000000..31d908df1 --- /dev/null +++ b/lib/odp/odp_manager_factory.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from "../shared_types"; +import { Cache } from "../utils/cache/cache"; +import { InMemoryLruCache } from "../utils/cache/in_memory_lru_cache"; +import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import { DefaultOdpEventApiManager, EventRequestGenerator } from "./event_manager/odp_event_api_manager"; +import { DefaultOdpEventManager, OdpEventManager } from "./event_manager/odp_event_manager"; +import { DefaultOdpManager, OdpManager } from "./odp_manager"; +import { DefaultOdpSegmentApiManager } from "./segment_manager/odp_segment_api_manager"; +import { DefaultOdpSegmentManager, OdpSegmentManager } from "./segment_manager/odp_segment_manager"; +import { UserAgentParser } from "./ua_parser/user_agent_parser"; + +export const DEFAULT_CACHE_SIZE = 1000; +export const DEFAULT_CACHE_TIMEOUT = 600_000; + +export const DEFAULT_EVENT_BATCH_SIZE = 100; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 10_000; +export const DEFAULT_EVENT_MAX_RETRIES = 5; +export const DEFAULT_EVENT_MIN_BACKOFF = 1000; +export const DEFAULT_EVENT_MAX_BACKOFF = 32_000; + +export type OdpManagerOptions = { + segmentsCache?: Cache<string[]>; + segmentsCacheSize?: number; + segmentsCacheTimeout?: number; + segmentsApiTimeout?: number; + segmentManager?: OdpSegmentManager; + eventFlushInterval?: number; + eventBatchSize?: number; + eventApiTimeout?: number; + eventManager?: OdpEventManager; + userAgentParser?: UserAgentParser; +}; + +export type OdpManagerFactoryOptions = Omit<OdpManagerOptions, 'segmentsApiTimeout' | 'eventApiTimeout'> & { + segmentRequestHandler: RequestHandler; + eventRequestHandler: RequestHandler; + eventRequestGenerator: EventRequestGenerator; + eventMaxRetries?: number; + eventMinBackoff?: number; + eventMaxBackoff?: number; +} + +const getDefaultSegmentsCache = (cacheSize?: number, cacheTimeout?: number) => { + return new InMemoryLruCache<string[]>(cacheSize || DEFAULT_CACHE_SIZE, cacheTimeout || DEFAULT_CACHE_TIMEOUT); +} + +const getDefaultSegmentManager = (options: OdpManagerFactoryOptions) => { + return new DefaultOdpSegmentManager( + options.segmentsCache || getDefaultSegmentsCache(options.segmentsCacheSize, options.segmentsCacheTimeout), + new DefaultOdpSegmentApiManager(options.segmentRequestHandler), + ); +}; + +const getDefaultEventManager = (options: OdpManagerFactoryOptions) => { + return new DefaultOdpEventManager({ + apiManager: new DefaultOdpEventApiManager(options.eventRequestHandler, options.eventRequestGenerator), + batchSize: options.eventBatchSize || DEFAULT_EVENT_BATCH_SIZE, + repeater: new IntervalRepeater(options.eventFlushInterval || DEFAULT_EVENT_FLUSH_INTERVAL), + retryConfig: { + maxRetries: options.eventMaxRetries || DEFAULT_EVENT_MAX_RETRIES, + backoffProvider: () => new ExponentialBackoff( + options.eventMinBackoff || DEFAULT_EVENT_MIN_BACKOFF, + options.eventMaxBackoff || DEFAULT_EVENT_MAX_BACKOFF, + 500, + ), + }, + }); +} + +export const getOdpManager = (options: OdpManagerFactoryOptions): OdpManager => { + const segmentManager = options.segmentManager || getDefaultSegmentManager(options); + const eventManager = options.eventManager || getDefaultEventManager(options); + + return new DefaultOdpManager({ + segmentManager, + eventManager, + userAgentParser: options.userAgentParser, + }); +}; diff --git a/lib/odp/odp_types.ts b/lib/odp/odp_types.ts index bd3e8217e..abe47b245 100644 --- a/lib/odp/odp_types.ts +++ b/lib/odp/odp_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/odp/odp_response_schema.ts b/lib/odp/segment_manager/odp_response_schema.ts similarity index 99% rename from lib/odp/odp_response_schema.ts rename to lib/odp/segment_manager/odp_response_schema.ts index 9aad4ac35..4221178af 100644 --- a/lib/odp/odp_response_schema.ts +++ b/lib/odp/segment_manager/odp_response_schema.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. diff --git a/lib/odp/segment_manager/odp_segment_api_manager.spec.ts b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts new file mode 100644 index 000000000..52237add9 --- /dev/null +++ b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts @@ -0,0 +1,245 @@ +/** + * Copyright 2022-2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; + +import { ODP_USER_KEY } from '../constant'; +import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { DefaultOdpSegmentApiManager } from './odp_segment_api_manager'; + +const API_KEY = 'not-real-api-key'; +const GRAPHQL_ENDPOINT = 'https://some.example.com/graphql/endpoint'; +const USER_KEY = ODP_USER_KEY.FS_USER_ID; +const USER_VALUE = 'tester-101'; +const SEGMENTS_TO_CHECK = ['has_email', 'has_email_opted_in', 'push_on_sale']; + +describe('DefaultOdpSegmentApiManager', () => { + it('should return empty list without calling api when segmentsToCheck is empty', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: '' }), + }); + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, []); + + expect(segments).toEqual([]); + expect(requestHandler.makeRequest).not.toHaveBeenCalled(); + }); + + it('should return null and log error if requestHandler promise rejects', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.reject(new Error('Request timed out')), + }); + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should log error and return null in case of non 200 HTTP status code response', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 500, body: '' }), + }); + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should return null and log error if response body is invalid JSON', async () => { + const invalidJsonResponse = 'not-a-valid-json-response'; + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: invalidJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should return null and log error if response body is unrecognized JSON', async () => { + const invalidJsonResponse = '{"a":1}'; + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: invalidJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should log error and return null in case of invalid identifier error response', async () => { + const INVALID_USER_ID = 'invalid-user'; + const errorJsonResponse = + '{"errors":[{"message":' + + '"Exception while fetching data (/customer) : ' + + `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + + '"locations":[{"line":1,"column":8}],"path":["customer"],' + + '"extensions":{"code": "INVALID_IDENTIFIER_EXCEPTION","classification":"DataFetchingException"}}],' + + '"data":{"customer":null}}'; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: errorJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, 'mock_user_id', SEGMENTS_TO_CHECK); + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledWith('Audience segments fetch failed (invalid identifier)'); + }); + + it('should log error and return null in case of errors other than invalid identifier error', async () => { + const INVALID_USER_ID = 'invalid-user'; + const errorJsonResponse = + '{"errors":[{"message":' + + '"Exception while fetching data (/customer) : ' + + `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + + '"locations":[{"line":1,"column":8}],"path":["customer"],' + + '"extensions":{"classification":"DataFetchingException"}}],' + + '"data":{"customer":null}}'; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: errorJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, 'mock_user_id', SEGMENTS_TO_CHECK); + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledWith('Audience segments fetch failed (DataFetchingException)'); + }); + + it('should log error and return null in case of response with invalid falsy edges field', async () => { + const jsonResponse = `{ + "data": { + "customer": { + "audiences": { + } + } + } + }`; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: jsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should parse a success response and return qualified segments', async () => { + const validJsonResponse = `{ + "data": { + "customer": { + "audiences": { + "edges": [ + { + "node": { + "name": "has_email", + "state": "qualified" + } + }, + { + "node": { + "name": "has_email_opted_in", + "state": "not-qualified" + } + } + ] + } + } + } + }`; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: validJsonResponse }), + }); + + const manager = new DefaultOdpSegmentApiManager(requestHandler); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toEqual(['has_email']); + }); + + it('should handle empty qualified segments', async () => { + const responseJsonWithNoQualifiedSegments = '{"data":{"customer":{"audiences":' + '{"edges":[ ]}}}}'; + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: responseJsonWithNoQualifiedSegments }), + }); + + const manager = new DefaultOdpSegmentApiManager(requestHandler); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toEqual([]); + }); + + it('should construct a valid GraphQL query request', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: '' }), + }); + + const manager = new DefaultOdpSegmentApiManager(requestHandler); + await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(requestHandler.makeRequest).toHaveBeenCalledWith( + `${GRAPHQL_ENDPOINT}/v3/graphql`, + { + 'Content-Type': 'application/json', + 'x-api-key': API_KEY, + }, + 'POST', + `{"query" : "query {customer(${USER_KEY} : \\"${USER_VALUE}\\") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"]) {edges {node {name state}}}}}"}` + ); + }); +}); diff --git a/lib/odp/segment_manager/odp_segment_api_manager.ts b/lib/odp/segment_manager/odp_segment_api_manager.ts index afe20ae2a..6b609a8a3 100644 --- a/lib/odp/segment_manager/odp_segment_api_manager.ts +++ b/lib/odp/segment_manager/odp_segment_api_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,12 @@ * limitations under the License. */ -import { LogHandler, LogLevel } from '../../modules/logging'; +import { LoggerFacade, LogLevel } from '../../modules/logging'; import { validate } from '../../utils/json_schema_validator'; -import { OdpResponseSchema } from '../odp_response_schema'; -import { ODP_USER_KEY } from '../../utils/enums'; -import { RequestHandler, Response as HttpResponse } from '../../utils/http_request_handler/http'; +import { OdpResponseSchema } from './odp_response_schema'; +import { ODP_USER_KEY } from '../constant'; +import { RequestHandler } from '../../utils/http_request_handler/http'; import { Response as GraphQLResponse } from '../odp_types'; - /** * Expected value for a qualified/valid segment */ @@ -41,7 +40,7 @@ const AUDIENCE_FETCH_FAILURE_MESSAGE = 'Audience segments fetch failed'; /** * Manager for communicating with the Optimizely Data Platform GraphQL endpoint */ -export interface IOdpSegmentApiManager { +export interface OdpSegmentApiManager { fetchSegments( apiKey: string, apiHost: string, @@ -51,19 +50,11 @@ export interface IOdpSegmentApiManager { ): Promise<string[] | null>; } -/** - * Concrete implementation for communicating with the ODP GraphQL endpoint - */ -export class OdpSegmentApiManager implements IOdpSegmentApiManager { - private readonly logger: LogHandler; +export class DefaultOdpSegmentApiManager implements OdpSegmentApiManager { + private readonly logger?: LoggerFacade; private readonly requestHandler: RequestHandler; - /** - * Communicates with Optimizely Data Platform's GraphQL endpoint - * @param requestHandler Desired request handler for testing - * @param logger Collect and record events/errors for this GraphQL implementation - */ - constructor(requestHandler: RequestHandler, logger: LogHandler) { + constructor(requestHandler: RequestHandler, logger?: LoggerFacade) { this.requestHandler = requestHandler; this.logger = logger; } @@ -83,11 +74,6 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { userValue: string, segmentsToCheck: string[] ): Promise<string[] | null> { - if (!apiKey || !apiHost) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (Parameters apiKey or apiHost invalid)`); - return null; - } - if (segmentsToCheck?.length === 0) { return EMPTY_SEGMENTS_COLLECTION; } @@ -95,15 +81,15 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { const endpoint = `${apiHost}/v3/graphql`; const query = this.toGraphQLJson(userKey, userValue, segmentsToCheck); - const segmentsResponse = await this.querySegments(apiKey, endpoint, userKey, userValue, query); + const segmentsResponse = await this.querySegments(apiKey, endpoint, query); if (!segmentsResponse) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (network error)`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (network error)`); return null; } const parsedSegments = this.parseSegmentsResponseJson(segmentsResponse); if (!parsedSegments) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); return null; } @@ -111,9 +97,9 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { const { code, classification } = parsedSegments.errors[0].extensions; if (code == 'INVALID_IDENTIFIER_EXCEPTION') { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (invalid identifier)`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (invalid identifier)`); } else { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (${classification})`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (${classification})`); } return null; @@ -121,7 +107,7 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { const edges = parsedSegments?.data?.customer?.audiences?.edges; if (!edges) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); return null; } @@ -156,8 +142,6 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { private async querySegments( apiKey: string, endpoint: string, - userKey: string, - userValue: string, query: string ): Promise<string | null> { const method = 'POST'; @@ -167,15 +151,16 @@ export class OdpSegmentApiManager implements IOdpSegmentApiManager { 'x-api-key': apiKey, }; - let response: HttpResponse; try { const request = this.requestHandler.makeRequest(url, headers, method, query); - response = await request.responsePromise; + const { statusCode, body} = await request.responsePromise; + if (!(statusCode >= 200 && statusCode < 300)) { + return null; + } + return body; } catch { return null; } - - return response.body; } /** diff --git a/lib/odp/segment_manager/odp_segment_manager.spec.ts b/lib/odp/segment_manager/odp_segment_manager.spec.ts new file mode 100644 index 000000000..31598dd71 --- /dev/null +++ b/lib/odp/segment_manager/odp_segment_manager.spec.ts @@ -0,0 +1,180 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; + + +import { ODP_USER_KEY } from '../constant'; +import { DefaultOdpSegmentManager } from './odp_segment_manager'; +import { OdpConfig } from '../odp_config'; +import { OptimizelySegmentOption } from './optimizely_segment_option'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { getMockSyncCache } from '../../tests/mock/mock_cache'; + +const API_KEY = 'test-api-key'; +const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; +const SEGMENTS_TO_CHECK = ['segment1', 'segment2']; + +const config = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, SEGMENTS_TO_CHECK); + +const getMockApiManager = () => { + return { + fetchSegments: vi.fn(), + }; +}; + +const userKey: ODP_USER_KEY = ODP_USER_KEY.FS_USER_ID; +const userValue = 'test-user'; + +describe('DefaultOdpSegmentManager', () => { + it('should return null and log error if the ODP config is not available.', async () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager(), logger); + expect(await manager.fetchQualifiedSegments(userKey, userValue)).toBeNull(); + expect(logger.warn).toHaveBeenCalledOnce(); + }); + + it('should return null and log error if ODP is not integrated.', async () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager(), logger); + manager.updateConfig({ integrated: false }); + expect(await manager.fetchQualifiedSegments(userKey, userValue)).toBeNull(); + expect(logger.warn).toHaveBeenCalledOnce(); + }); + + it('should fetch segments from apiManager using correct config on cache miss and save to cache.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue); + expect(segments).toEqual(['k', 'l']); + expect(apiManager.fetchSegments).toHaveBeenCalledWith(API_KEY, API_HOST, userKey, userValue, SEGMENTS_TO_CHECK); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['k', 'l']); + }); + + it('should return segment from cache and not call apiManager on cache hit.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue); + expect(segments).toEqual(['x']); + + expect(apiManager.fetchSegments).not.toHaveBeenCalled(); + }); + + it('should return null when apiManager returns null.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(null); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue); + expect(segments).toBeNull(); + }); + + it('should ignore the cache if the option enum is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, [OptimizelySegmentOption.IGNORE_CACHE]); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['x']); + expect(apiManager.fetchSegments).toHaveBeenCalledWith(API_KEY, API_HOST, userKey, userValue, SEGMENTS_TO_CHECK); + }); + + it('should ignore the cache if the option string is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const segments = await manager.fetchQualifiedSegments(userKey, userValue, ['IGNORE_CACHE']); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['x']); + expect(apiManager.fetchSegments).toHaveBeenCalledWith(API_KEY, API_HOST, userKey, userValue, SEGMENTS_TO_CHECK); + }); + + it('should reset the cache if the option enum is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + cache.set(manager.makeCacheKey(userKey, '123'), ['a']); + cache.set(manager.makeCacheKey(userKey, '456'), ['b']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, [OptimizelySegmentOption.RESET_CACHE]); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['k', 'l']); + expect(cache.size()).toBe(1); + }); + + it('should reset the cache if the option string is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + cache.set(manager.makeCacheKey(userKey, '123'), ['a']); + cache.set(manager.makeCacheKey(userKey, '456'), ['b']); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const segments = await manager.fetchQualifiedSegments(userKey, userValue, ['RESET_CACHE']); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['k', 'l']); + expect(cache.size()).toBe(1); + }); + + it('should reset the cache on config update.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + cache.set(manager.makeCacheKey(userKey, '123'), ['a']); + cache.set(manager.makeCacheKey(userKey, '456'), ['b']); + + expect(cache.size()).toBe(3); + manager.updateConfig({ integrated: true, odpConfig: config }); + expect(cache.size()).toBe(0); + }); +}); diff --git a/lib/odp/segment_manager/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts index 4aaa47dc3..dbf83a12f 100644 --- a/lib/odp/segment_manager/odp_segment_manager.ts +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -14,70 +14,37 @@ * limitations under the License. */ -import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; -import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; -import { ICache } from '../../utils/lru_cache'; -import { IOdpSegmentApiManager } from './odp_segment_api_manager'; -import { OdpConfig } from '../odp_config'; +import { ERROR_MESSAGES } from '../../utils/enums'; +import { Cache } from '../../utils/cache/cache'; +import { OdpSegmentApiManager } from './odp_segment_api_manager'; +import { OdpIntegrationConfig } from '../odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; +import { ODP_USER_KEY } from '../constant'; +import { LoggerFacade } from '../../modules/logging'; -export interface IOdpSegmentManager { +export interface OdpSegmentManager { fetchQualifiedSegments( userKey: ODP_USER_KEY, userValue: string, - options: Array<OptimizelySegmentOption> + options?: Array<OptimizelySegmentOption> ): Promise<string[] | null>; - reset(): void; - makeCacheKey(userKey: string, userValue: string): string; - updateSettings(config: OdpConfig): void; + updateConfig(config: OdpIntegrationConfig): void; } -/** - * Schedules connections to ODP for audience segmentation and caches the results. - */ -export class OdpSegmentManager implements IOdpSegmentManager { - /** - * ODP configuration settings in used - * @private - */ - private odpConfig?: OdpConfig; - - /** - * Holds cached audience segments - * @private - */ - private _segmentsCache: ICache<string, string[]>; - - /** - * Getter for private segments cache - * @public - */ - get segmentsCache(): ICache<string, string[]> { - return this._segmentsCache; - } - - /** - * GraphQL API Manager used to fetch segments - * @private - */ - private odpSegmentApiManager: IOdpSegmentApiManager; - - /** - * Handler for recording execution logs - * @private - */ - private readonly logger: LogHandler; +export class DefaultOdpSegmentManager implements OdpSegmentManager { + private odpIntegrationConfig?: OdpIntegrationConfig; + private segmentsCache: Cache<string[]>; + private odpSegmentApiManager: OdpSegmentApiManager + private logger?: LoggerFacade; constructor( - segmentsCache: ICache<string, string[]>, - odpSegmentApiManager: IOdpSegmentApiManager, - logger?: LogHandler, - odpConfig?: OdpConfig, + segmentsCache: Cache<string[]>, + odpSegmentApiManager: OdpSegmentApiManager, + logger?: LoggerFacade, ) { - this.odpConfig = odpConfig; - this._segmentsCache = segmentsCache; + this.segmentsCache = segmentsCache; this.odpSegmentApiManager = odpSegmentApiManager; - this.logger = logger || getLogger('OdpSegmentManager'); + this.logger = logger; } /** @@ -91,77 +58,62 @@ export class OdpSegmentManager implements IOdpSegmentManager { async fetchQualifiedSegments( userKey: ODP_USER_KEY, userValue: string, - options: Array<OptimizelySegmentOption> + options?: Array<OptimizelySegmentOption> ): Promise<string[] | null> { - if (!this.odpConfig) { - this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + if (!this.odpIntegrationConfig) { + this.logger?.warn(ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return null; } - const segmentsToCheck = this.odpConfig.segmentsToCheck; + if (!this.odpIntegrationConfig.integrated) { + this.logger?.warn(ERROR_MESSAGES.ODP_NOT_INTEGRATED); + return null; + } + + const odpConfig = this.odpIntegrationConfig.odpConfig; + + const segmentsToCheck = odpConfig.segmentsToCheck; if (!segmentsToCheck || segmentsToCheck.length <= 0) { - this.logger.log(LogLevel.DEBUG, 'No segments are used in the project. Returning an empty list.'); return []; } const cacheKey = this.makeCacheKey(userKey, userValue); - const ignoreCache = options.includes(OptimizelySegmentOption.IGNORE_CACHE); - const resetCache = options.includes(OptimizelySegmentOption.RESET_CACHE); + const ignoreCache = options?.includes(OptimizelySegmentOption.IGNORE_CACHE); + const resetCache = options?.includes(OptimizelySegmentOption.RESET_CACHE); if (resetCache) { - this.reset(); + this.segmentsCache.clear(); } - if (!ignoreCache && !resetCache) { - const cachedSegments = this._segmentsCache.lookup(cacheKey); + if (!ignoreCache) { + const cachedSegments = await this.segmentsCache.get(cacheKey); if (cachedSegments) { - this.logger.log(LogLevel.DEBUG, 'ODP cache hit. Returning segments from cache "%s".', cacheKey); return cachedSegments; } - this.logger.log(LogLevel.DEBUG, `ODP cache miss.`); } - this.logger.log(LogLevel.DEBUG, `Making a call to ODP server.`); - const segments = await this.odpSegmentApiManager.fetchSegments( - this.odpConfig.apiKey, - this.odpConfig.apiHost, + odpConfig.apiKey, + odpConfig.apiHost, userKey, userValue, segmentsToCheck ); if (segments && !ignoreCache) { - this._segmentsCache.save({ key: cacheKey, value: segments }); + this.segmentsCache.set(cacheKey, segments); } return segments; } - /** - * Clears the segments cache - */ - reset(): void { - this._segmentsCache.reset(); - } - - /** - * Creates a key used to identify which user fetchQualifiedSegments should lookup and save to in the segments cache - * @param userKey User type based on ODP_USER_KEY, such as "vuid" or "fs_user_id" - * @param userValue Arbitrary string, such as "test-user" - * @returns Concatenates inputs and returns the string "{userKey}-$-{userValue}" - */ makeCacheKey(userKey: string, userValue: string): string { return `${userKey}-$-${userValue}`; } - /** - * Updates the ODP Config settings of ODP Segment Manager - * @param config New ODP Config that will overwrite the existing config - */ - updateSettings(config: OdpConfig): void { - this.odpConfig = config; - this.reset(); + updateConfig(config: OdpIntegrationConfig): void { + this.odpIntegrationConfig = config; + this.segmentsCache.clear(); } } diff --git a/lib/odp/segment_manager/optimizely_segment_option.ts b/lib/odp/segment_manager/optimizely_segment_option.ts index 112cd39cc..cf7c801ef 100644 --- a/lib/odp/segment_manager/optimizely_segment_option.ts +++ b/lib/odp/segment_manager/optimizely_segment_option.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/odp/ua_parser/ua_parser.browser.ts b/lib/odp/ua_parser/ua_parser.browser.ts index e6cc27dc8..522c538be 100644 --- a/lib/odp/ua_parser/ua_parser.browser.ts +++ b/lib/odp/ua_parser/ua_parser.browser.ts @@ -16,9 +16,9 @@ import { UAParser } from 'ua-parser-js'; import { UserAgentInfo } from './user_agent_info'; -import { IUserAgentParser } from './user_agent_parser'; +import { UserAgentParser } from './user_agent_parser'; -const userAgentParser: IUserAgentParser = { +const userAgentParser: UserAgentParser = { parseUserAgentInfo(): UserAgentInfo { const parser = new UAParser(); const agentInfo = parser.getResult(); @@ -27,7 +27,7 @@ const userAgentParser: IUserAgentParser = { } } -export function getUserAgentParser(): IUserAgentParser { +export function getUserAgentParser(): UserAgentParser { return userAgentParser; } diff --git a/lib/odp/ua_parser/user_agent_parser.ts b/lib/odp/ua_parser/user_agent_parser.ts index 227065fb7..9ca30c141 100644 --- a/lib/odp/ua_parser/user_agent_parser.ts +++ b/lib/odp/ua_parser/user_agent_parser.ts @@ -16,6 +16,6 @@ import { UserAgentInfo } from "./user_agent_info"; -export interface IUserAgentParser { +export interface UserAgentParser { parseUserAgentInfo(): UserAgentInfo, } diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index ee1525e2d..364c05658 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -25,8 +25,9 @@ import testData from '../tests/test_data'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; import { LoggerFacade } from '../modules/logging'; import { createProjectConfig } from '../project_config/project_config'; +import { getMockLogger } from '../tests/mock/mock_logger'; -describe('lib/optimizely', () => { +describe('Optimizely', () => { const errorHandler = { handleError: function() {} }; const eventDispatcher = { @@ -35,18 +36,9 @@ describe('lib/optimizely', () => { const eventProcessor = getForwardingEventProcessor(eventDispatcher); - const createdLogger: LoggerFacade = { - ...logger.createLogger({ - logLevel: LOG_LEVEL.INFO, - }), - info: () => {}, - debug: () => {}, - warn: () => {}, - error: () => {}, - log: () => {}, - }; + const logger = getMockLogger(); - const notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); + const notificationCenter = createNotificationCenter({ logger, errorHandler }); it('should pass ssr to the project config manager', () => { const projectConfigManager = getMockProjectConfigManager({ @@ -60,7 +52,7 @@ describe('lib/optimizely', () => { projectConfigManager, errorHandler, jsonSchemaValidator, - logger: createdLogger, + logger, notificationCenter, eventProcessor, isSsr: true, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 7628a0a17..4c4898c91 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -19,7 +19,8 @@ import { sprintf, objectValues } from '../utils/fns'; import { DefaultNotificationCenter, NotificationCenter } from '../notification_center'; import { EventProcessor } from '../event_processor/event_processor'; -import { IOdpManager } from '../odp/odp_manager'; +import { OdpManager } from '../odp/odp_manager'; +import { VuidManager } from '../vuid/vuid_manager'; import { OdpEvent } from '../odp/event_manager/odp_event'; import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; @@ -41,7 +42,6 @@ import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; import { ProjectConfigManager } from '../project_config/project_config_manager'; import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; -// import { getImpressionEvent, getConversionEvent } from '../event_processor/event_builder'; import { buildLogEvent } from '../event_processor/event_builder/log_event'; import { buildImpressionEvent, buildConversionEvent, ImpressionEvent } from '../event_processor/event_builder/user_event'; import fns from '../utils/fns'; @@ -63,9 +63,6 @@ import { // NOTIFICATION_TYPES, NODE_CLIENT_ENGINE, CLIENT_VERSION, - ODP_DEFAULT_EVENT_TYPE, - FS_USER_ID_ALIAS, - ODP_USER_KEY, } from '../utils/enums'; import { Fn } from '../utils/type'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; @@ -94,13 +91,14 @@ export default class Optimizely implements Client { private clientEngine: string; private clientVersion: string; private errorHandler: ErrorHandler; - protected logger: LoggerFacade; + private logger: LoggerFacade; private projectConfigManager: ProjectConfigManager; private decisionService: DecisionService; private eventProcessor?: EventProcessor; private defaultDecideOptions: { [key: string]: boolean }; - protected odpManager?: IOdpManager; + private odpManager?: OdpManager; public notificationCenter: DefaultNotificationCenter; + private vuidManager?: VuidManager; constructor(config: OptimizelyOptions) { let clientEngine = config.clientEngine; @@ -115,6 +113,7 @@ export default class Optimizely implements Client { this.isOptimizelyConfigValid = config.isValidInstance; this.logger = config.logger; this.odpManager = config.odpManager; + this.vuidManager = config.vuidManager; let decideOptionsArray = config.defaultDecideOptions ?? []; if (!Array.isArray(decideOptionsArray)) { @@ -185,9 +184,17 @@ export default class Optimizely implements Client { this.readyPromise = Promise.all([ projectConfigManagerRunningPromise, eventProcessorRunningPromise, - config.odpManager ? config.odpManager.onReady() : Promise.resolve(), + config.odpManager ? config.odpManager.onRunning() : Promise.resolve(), + config.vuidManager ? config.vuidManager.initialize() : Promise.resolve(), ]); + this.readyPromise.then(() => { + const vuid = this.vuidManager?.getVuid(); + if (vuid) { + this.odpManager?.setVuid(vuid); + } + }); + this.readyTimeouts = {}; this.nextReadyTimeoutId = 0; } @@ -1230,13 +1237,10 @@ export default class Optimizely implements Client { */ close(): Promise<{ success: boolean; reason?: string }> { try { - if (this.odpManager) { - this.odpManager.stop(); - } - - this.notificationCenter.clearAllNotificationListeners(); - + this.projectConfigManager.stop(); this.eventProcessor?.stop(); + this.odpManager?.stop(); + this.notificationCenter.clearAllNotificationListeners(); const eventProcessorStoppedPromise = this.eventProcessor ? this.eventProcessor.onTerminated() : Promise.resolve(); @@ -1245,9 +1249,7 @@ export default class Optimizely implements Client { this.disposeOnUpdate(); this.disposeOnUpdate = undefined; } - if (this.projectConfigManager) { - this.projectConfigManager.stop(); - } + Object.keys(this.readyTimeouts).forEach((readyTimeoutId: string) => { const readyTimeoutRecord = this.readyTimeouts[readyTimeoutId]; clearTimeout(readyTimeoutRecord.readyTimeout); @@ -1358,7 +1360,7 @@ export default class Optimizely implements Client { * null if provided inputs are invalid */ createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { - const userIdentifier = userId ?? this.odpManager?.getVuid(); + const userIdentifier = userId ?? this.vuidManager?.getVuid(); if (userIdentifier === undefined || !this.validateInputs({ user_id: userIdentifier }, attributes)) { return null; @@ -1632,7 +1634,7 @@ export default class Optimizely implements Client { } if (this.odpManager) { - this.odpManager.updateSettings(projectConfig.odpIntegrationConfig); + this.odpManager.updateConfig(projectConfig.odpIntegrationConfig); } } @@ -1655,29 +1657,8 @@ export default class Optimizely implements Client { return; } - const odpEventType = type ?? ODP_DEFAULT_EVENT_TYPE; - - const odpIdentifiers = new Map(identifiers); - - if (identifiers && identifiers.size > 0) { - try { - identifiers.forEach((identifier_value, identifier_key) => { - // Catch for fs-user-id, FS-USER-ID, and FS_USER_ID and assign value to fs_user_id identifier. - if ( - FS_USER_ID_ALIAS === identifier_key.toLowerCase() || - ODP_USER_KEY.FS_USER_ID === identifier_key.toLowerCase() - ) { - odpIdentifiers.delete(identifier_key); - odpIdentifiers.set(ODP_USER_KEY.FS_USER_ID, identifier_value); - } - }); - } catch (e) { - this.logger.warn(LOG_MESSAGES.ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED); - } - } - try { - const odpEvent = new OdpEvent(odpEventType, action, odpIdentifiers, data); + const odpEvent = new OdpEvent(type || '', action, identifiers, data); this.odpManager.sendEvent(odpEvent); } catch (e) { this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED, e); @@ -1724,16 +1705,11 @@ export default class Optimizely implements Client { * ODP Manager has not been instantiated yet for any reason. */ public getVuid(): string | undefined { - if (!this.odpManager) { - this.logger?.error('Unable to get VUID - ODP Manager is not instantiated yet.'); - return undefined; - } - - if (!this.odpManager.isVuidEnabled()) { - this.logger.log(LOG_LEVEL.WARNING, 'getVuid() unavailable for this platform', MODULE_NAME); + if (!this.vuidManager) { + this.logger?.error('Unable to get VUID - VuidManager is not available'); return undefined; } - return this.odpManager.getVuid(); + return this.vuidManager.getVuid(); } } diff --git a/lib/plugins/key_value_cache/browserAsyncStorageCache.ts b/lib/plugins/key_value_cache/browserAsyncStorageCache.ts deleted file mode 100644 index 508a9e5f4..000000000 --- a/lib/plugins/key_value_cache/browserAsyncStorageCache.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { tryWithLocalStorage } from '../../utils/local_storage/tryLocalStorage'; -import PersistentKeyValueCache from './persistentKeyValueCache'; -import { getLogger } from '../../modules/logging'; -import { ERROR_MESSAGES } from './../../utils/enums/index'; - -export default class BrowserAsyncStorageCache implements PersistentKeyValueCache { - logger = getLogger(); - - async contains(key: string): Promise<boolean> { - return tryWithLocalStorage<boolean>({ - browserCallback: (localStorage?: Storage) => { - return localStorage?.getItem(key) !== null; - }, - nonBrowserCallback: () => { - this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); - return false; - }, - }); - } - - async get(key: string): Promise<string | undefined> { - return tryWithLocalStorage<string | undefined>({ - browserCallback: (localStorage?: Storage) => { - return (localStorage?.getItem(key) || undefined); - }, - nonBrowserCallback: () => { - this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); - return undefined; - }, - }); - } - - async remove(key: string): Promise<boolean> { - if (await this.contains(key)) { - tryWithLocalStorage({ - browserCallback: (localStorage?: Storage) => { - localStorage?.removeItem(key); - }, - nonBrowserCallback: () => { - this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); - }, - }); - return true; - } else { - return false; - } - } - - async set(key: string, val: string): Promise<void> { - return tryWithLocalStorage({ - browserCallback: (localStorage?: Storage) => { - localStorage?.setItem(key, val); - }, - nonBrowserCallback: () => { - this.logger.error(ERROR_MESSAGES.LOCAL_STORAGE_DOES_NOT_EXIST); - }, - }); - } -} diff --git a/lib/plugins/vuid_manager/index.ts b/lib/plugins/vuid_manager/index.ts deleted file mode 100644 index 8587724d6..000000000 --- a/lib/plugins/vuid_manager/index.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright 2022-2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { uuid } from '../../utils/fns'; -import PersistentKeyValueCache from '../key_value_cache/persistentKeyValueCache'; - -export interface IVuidManager { - readonly vuid: string; -} - -/** - * Manager for creating, persisting, and retrieving a Visitor Unique Identifier - */ -export class VuidManager implements IVuidManager { - /** - * Prefix used as part of the VUID format - * @public - * @readonly - */ - static readonly vuid_prefix: string = `vuid_`; - - /** - * Unique key used within the persistent value cache against which to - * store the VUID - * @private - */ - private _keyForVuid = 'optimizely-vuid'; - - /** - * Current VUID value being used - * @private - */ - private _vuid: string; - - /** - * Get the current VUID value being used - */ - get vuid(): string { - return this._vuid; - } - - private constructor() { - this._vuid = ''; - } - - /** - * Instance of the VUID Manager - * @private - */ - private static _instance: VuidManager; - - /** - * Gets the current instance of the VUID Manager, initializing if needed - * @param cache Caching mechanism to use for persisting the VUID outside working memory * - * @returns An instance of VuidManager - */ - static async instance(cache: PersistentKeyValueCache): Promise<VuidManager> { - if (!this._instance) { - this._instance = new VuidManager(); - } - - if (!this._instance._vuid) { - await this._instance.load(cache); - } - - return this._instance; - } - - /** - * Attempts to load a VUID from persistent cache or generates a new VUID - * @param cache Caching mechanism to use for persisting the VUID outside working memory - * @returns Current VUID stored in the VuidManager - * @private - */ - private async load(cache: PersistentKeyValueCache): Promise<string> { - const cachedValue = await cache.get(this._keyForVuid); - if (cachedValue && VuidManager.isVuid(cachedValue)) { - this._vuid = cachedValue; - } else { - this._vuid = this.makeVuid(); - await this.save(this._vuid, cache); - } - - return this._vuid; - } - - /** - * Creates a new VUID - * @returns A new visitor unique identifier - * @private - */ - private makeVuid(): string { - const maxLength = 32; // required by ODP server - - // make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details. - const uuidV4 = uuid(); - const formatted = uuidV4.replace(/-/g, '').toLowerCase(); - const vuidFull = `${VuidManager.vuid_prefix}${formatted}`; - - return vuidFull.length <= maxLength ? vuidFull : vuidFull.substring(0, maxLength); - } - - /** - * Saves a VUID to a persistent cache - * @param vuid VUID to be stored - * @param cache Caching mechanism to use for persisting the VUID outside working memory - * @private - */ - private async save(vuid: string, cache: PersistentKeyValueCache): Promise<void> { - await cache.set(this._keyForVuid, vuid); - } - - /** - * Validates the format of a Visitor Unique Identifier - * @param vuid VistorId to check - * @returns *true* if the VisitorId is valid otherwise *false* for invalid - */ - static isVuid = (vuid: string): boolean => vuid?.startsWith(VuidManager.vuid_prefix) || false; - - /** - * Function used in unit testing to reset the VuidManager - * **Important**: This should not to be used in production code - * @private - */ - private static _reset(): void { - this._instance._vuid = ''; - } -} diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 2cab1c052..fa3579e69 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -25,24 +25,24 @@ import { NotificationCenter, DefaultNotificationCenter } from './notification_ce import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; -import { ICache } from './utils/lru_cache'; import { RequestHandler } from './utils/http_request_handler/http'; import { OptimizelySegmentOption } from './odp/segment_manager/optimizely_segment_option'; -import { IOdpSegmentApiManager } from './odp/segment_manager/odp_segment_api_manager'; -import { IOdpSegmentManager } from './odp/segment_manager/odp_segment_manager'; -import { IOdpEventApiManager } from './odp/event_manager/odp_event_api_manager'; -import { IOdpEventManager } from './odp/event_manager/odp_event_manager'; -import { IOdpManager } from './odp/odp_manager'; -import { IUserAgentParser } from './odp/ua_parser/user_agent_parser'; +import { OdpSegmentApiManager } from './odp/segment_manager/odp_segment_api_manager'; +import { OdpSegmentManager } from './odp/segment_manager/odp_segment_manager'; +import { DefaultOdpEventApiManager } from './odp/event_manager/odp_event_api_manager'; +import { OdpEventManager } from './odp/event_manager/odp_event_manager'; +import { OdpManager } from './odp/odp_manager'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; import { ProjectConfig } from './project_config/project_config'; import { ProjectConfigManager } from './project_config/project_config_manager'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor/event_processor'; +import { VuidManager } from './vuid/vuid_manager'; export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; export { EventProcessor } from './event_processor/event_processor'; export { NotificationCenter } from './notification_center'; +export { VuidManager } from './vuid/vuid_manager'; export interface BucketerParams { experimentId: string; @@ -99,23 +99,6 @@ export interface DatafileOptions { datafileAccessToken?: string; } -export interface OdpOptions { - disabled?: boolean; - segmentsCache?: ICache<string, string[]>; - segmentsCacheSize?: number; - segmentsCacheTimeout?: number; - segmentsApiTimeout?: number; - segmentsRequestHandler?: RequestHandler; - segmentManager?: IOdpSegmentManager; - eventFlushInterval?: number; - eventBatchSize?: number; - eventQueueSize?: number; - eventApiTimeout?: number; - eventRequestHandler?: RequestHandler; - eventManager?: IOdpEventManager; - userAgentParser?: IUserAgentParser; -} - export interface ListenerPayload { userId: string; attributes?: UserAttributes; @@ -282,8 +265,9 @@ export interface OptimizelyOptions { userProfileService?: UserProfileService | null; defaultDecideOptions?: OptimizelyDecideOption[]; isSsr?:boolean; - odpManager?: IOdpManager; + odpManager?: OdpManager; notificationCenter: DefaultNotificationCenter; + vuidManager?: VuidManager } /** @@ -386,7 +370,6 @@ export interface Config extends ConfigLite { // eventFlushInterval?: number; // Maximum time for an event to be enqueued // eventMaxQueueSize?: number; // Maximum size for the event queue sdkKey?: string; - odpOptions?: OdpOptions; persistentCacheProvider?: PersistentCacheProvider; } @@ -417,6 +400,8 @@ export interface ConfigLite { clientEngine?: string; clientVersion?: string; isSsr?: boolean; + odpManager?: OdpManager; + vuidManager?: VuidManager; } export type OptimizelyExperimentsMap = { @@ -539,12 +524,11 @@ export interface OptimizelyForcedDecision { // ODP Exports export { - ICache, RequestHandler, OptimizelySegmentOption, - IOdpSegmentApiManager, - IOdpSegmentManager, - IOdpEventApiManager, - IOdpEventManager, - IOdpManager, + OdpSegmentApiManager, + OdpSegmentManager, + DefaultOdpEventApiManager, + OdpEventManager, + OdpManager, }; diff --git a/lib/tests/mock/mock_repeater.ts b/lib/tests/mock/mock_repeater.ts index a93cbfa87..adf6baf83 100644 --- a/lib/tests/mock/mock_repeater.ts +++ b/lib/tests/mock/mock_repeater.ts @@ -20,7 +20,7 @@ import { AsyncTransformer } from '../../utils/type'; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const getMockRepeater = () => { const mock = { - isRunning: false, + running: false, handler: undefined as any, start: vi.fn(), stop: vi.fn(), @@ -36,8 +36,9 @@ export const getMockRepeater = () => { ret?.catch(() => {}); return ret; }, + isRunning: () => mock.running, }; - mock.start.mockImplementation(() => mock.isRunning = true); - mock.stop.mockImplementation(() => mock.isRunning = false); + mock.start.mockImplementation(() => mock.running = true); + mock.stop.mockImplementation(() => mock.running = false); return mock; } diff --git a/lib/tests/testUtils.ts b/lib/tests/testUtils.ts new file mode 100644 index 000000000..8bcd093f8 --- /dev/null +++ b/lib/tests/testUtils.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const exhaustMicrotasks = async (loop = 100): Promise<void> => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +}; + +export const wait = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/lib/utils/cache/in_memory_lru_cache.spec.ts b/lib/utils/cache/in_memory_lru_cache.spec.ts new file mode 100644 index 000000000..c6ab08780 --- /dev/null +++ b/lib/utils/cache/in_memory_lru_cache.spec.ts @@ -0,0 +1,124 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it } from 'vitest'; +import { InMemoryLruCache } from './in_memory_lru_cache'; +import { wait } from '../../tests/testUtils'; + +describe('InMemoryLruCache', () => { + it('should save and get values correctly', () => { + const cache = new InMemoryLruCache<number>(2); + cache.set('a', 1); + cache.set('b', 2); + expect(cache.get('a')).toBe(1); + expect(cache.get('b')).toBe(2); + }); + + it('should return undefined for non-existent keys', () => { + const cache = new InMemoryLruCache<number>(2); + expect(cache.get('a')).toBe(undefined); + }); + + it('should return all keys in cache when getKeys is called', () => { + const cache = new InMemoryLruCache<number>(20); + cache.set('a', 1); + cache.set('b', 2); + cache.set('c', 3); + cache.set('d', 4); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['d', 'c', 'b', 'a'])); + }); + + it('should evict least recently used keys when full', () => { + const cache = new InMemoryLruCache<number>(3); + cache.set('a', 1); + cache.set('b', 2); + cache.set('c', 3); + + expect(cache.get('b')).toBe(2); + expect(cache.get('c')).toBe(3); + expect(cache.get('a')).toBe(1); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['a', 'c', 'b'])); + + // key use order is now a c b. next insert should evict b + cache.set('d', 4); + expect(cache.get('b')).toBe(undefined); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['d', 'a', 'c'])); + + // key use order is now d a c. setting c should put it at the front + cache.set('c', 5); + + // key use order is now c d a. next insert should evict a + cache.set('e', 6); + expect(cache.get('a')).toBe(undefined); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['e', 'c', 'd'])); + + // key use order is now e c d. reading d should put it at the front + expect(cache.get('d')).toBe(4); + + // key use order is now d e c. next insert should evict c + cache.set('f', 7); + expect(cache.get('c')).toBe(undefined); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['f', 'd', 'e'])); + }); + + it('should not return expired values when get is called', async () => { + const cache = new InMemoryLruCache<number>(2, 100); + cache.set('a', 1); + cache.set('b', 2); + expect(cache.get('a')).toBe(1); + expect(cache.get('b')).toBe(2); + + await wait(150); + expect(cache.get('a')).toBe(undefined); + expect(cache.get('b')).toBe(undefined); + }); + + it('should remove values correctly', () => { + const cache = new InMemoryLruCache<number>(2); + cache.set('a', 1); + cache.set('b', 2); + cache.set('c', 3); + cache.remove('a'); + expect(cache.get('a')).toBe(undefined); + expect(cache.get('b')).toBe(2); + expect(cache.get('c')).toBe(3); + }); + + it('should clear all values correctly', () => { + const cache = new InMemoryLruCache<number>(2); + cache.set('a', 1); + cache.set('b', 2); + cache.clear(); + expect(cache.get('a')).toBe(undefined); + expect(cache.get('b')).toBe(undefined); + }); + + it('should return correct values when getBatched is called', () => { + const cache = new InMemoryLruCache<number>(2); + cache.set('a', 1); + cache.set('b', 2); + expect(cache.getBatched(['a', 'b', 'c'])).toEqual([1, 2, undefined]); + }); + + it('should not return expired values when getBatched is called', async () => { + const cache = new InMemoryLruCache<number>(2, 100); + cache.set('a', 1); + cache.set('b', 2); + expect(cache.getBatched(['a', 'b'])).toEqual([1, 2]); + + await wait(150); + expect(cache.getBatched(['a', 'b'])).toEqual([undefined, undefined]); + }); +}); diff --git a/lib/utils/cache/in_memory_lru_cache.ts b/lib/utils/cache/in_memory_lru_cache.ts new file mode 100644 index 000000000..1b4d3a7bd --- /dev/null +++ b/lib/utils/cache/in_memory_lru_cache.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Maybe } from "../type"; +import { SyncCache } from "./cache"; + +type CacheElement<V> = { + value: V; + expiresAt?: number; +}; + +export class InMemoryLruCache<V> implements SyncCache<V> { + public operation = 'sync' as const; + private data: Map<string, CacheElement<V>> = new Map(); + private maxSize: number; + private ttl?: number; + + constructor(maxSize: number, ttl?: number) { + this.maxSize = maxSize; + this.ttl = ttl; + } + + get(key: string): Maybe<V> { + const element = this.data.get(key); + if (!element) return undefined; + this.data.delete(key); + + if (element.expiresAt && element.expiresAt <= Date.now()) { + return undefined; + } + + this.data.set(key, element); + return element.value; + } + + set(key: string, value: V): void { + this.data.delete(key); + + if (this.data.size === this.maxSize) { + const firstMapEntryKey = this.data.keys().next().value; + this.data.delete(firstMapEntryKey!); + } + + this.data.set(key, { + value, + expiresAt: this.ttl ? Date.now() + this.ttl : undefined, + }); + } + + remove(key: string): void { + this.data.delete(key); + } + + clear(): void { + this.data.clear(); + } + + getKeys(): string[] { + return Array.from(this.data.keys()); + } + + getBatched(keys: string[]): Maybe<V>[] { + return keys.map((key) => this.get(key)); + } +} diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 10a5deb3f..551fa6b98 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -291,28 +291,6 @@ export { NOTIFICATION_TYPES } from '../../notification_center/type'; * Default milliseconds before request timeout */ export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute -export const REQUEST_TIMEOUT_ODP_SEGMENTS_MS = 10 * 1000; // 10 secs -export const REQUEST_TIMEOUT_ODP_EVENTS_MS = 10 * 1000; // 10 secs -/** - * ODP User Key Options - */ -export enum ODP_USER_KEY { - VUID = 'vuid', - FS_USER_ID = 'fs_user_id', -} -/** - * Alias for fs_user_id to catch for and automatically convert to fs_user_id - */ -export const FS_USER_ID_ALIAS = 'fs-user-id'; -export const ODP_DEFAULT_EVENT_TYPE = 'fullstack'; - -/** - * ODP Event Action Options - */ -export enum ODP_EVENT_ACTION { - IDENTIFIED = 'identified', - INITIALIZED = 'client_initialized', -} diff --git a/lib/utils/fns/index.ts b/lib/utils/fns/index.ts index 98606a77a..e53402a22 100644 --- a/lib/utils/fns/index.ts +++ b/lib/utils/fns/index.ts @@ -57,6 +57,7 @@ export function keyBy<K>(arr: K[], key: string): { [key: string]: K } { }); } + function isNumber(value: unknown): boolean { return typeof value === 'number'; } diff --git a/lib/utils/lru_cache/browser_lru_cache.ts b/lib/utils/lru_cache/browser_lru_cache.ts deleted file mode 100644 index ca5d4cb92..000000000 --- a/lib/utils/lru_cache/browser_lru_cache.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2022-2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import LRUCache, { ISegmentsCacheConfig } from './lru_cache'; - -export interface BrowserLRUCacheConfig { - maxSize?: number; - timeout?: number; -} - -export const BrowserLRUCacheConfig: ISegmentsCacheConfig = { - DEFAULT_CAPACITY: 100, - DEFAULT_TIMEOUT_SECS: 600, -}; - -export class BrowserLRUCache<K, V> extends LRUCache<K, V> { - constructor(config?: BrowserLRUCacheConfig) { - super({ - maxSize: config?.maxSize?? BrowserLRUCacheConfig.DEFAULT_CAPACITY, - timeout: config?.timeout?? BrowserLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, - }); - } -} diff --git a/lib/utils/lru_cache/cache_element.tests.ts b/lib/utils/lru_cache/cache_element.tests.ts deleted file mode 100644 index dfba16fa7..000000000 --- a/lib/utils/lru_cache/cache_element.tests.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assert } from 'chai'; -import { CacheElement } from './cache_element'; - -const sleep = async (ms: number) => { - return await new Promise(r => setTimeout(r, ms)); -}; - -describe('/odp/lru_cache/CacheElement', () => { - let element: CacheElement<string>; - - beforeEach(() => { - element = new CacheElement('foo'); - }); - - it('should initialize a valid CacheElement', () => { - assert.exists(element); - assert.equal(element.value, 'foo'); - assert.isNotNull(element.time); - assert.doesNotThrow(() => element.is_stale(0)); - }); - - it('should return false if not stale based on timeout', () => { - const timeoutLong = 1000; - assert.equal(element.is_stale(timeoutLong), false); - }); - - it('should return false if not stale because timeout is less than or equal to 0', () => { - const timeoutNone = 0; - assert.equal(element.is_stale(timeoutNone), false); - }); - - it('should return true if stale based on timeout', async () => { - await sleep(100); - const timeoutShort = 1; - assert.equal(element.is_stale(timeoutShort), true); - }); -}); diff --git a/lib/utils/lru_cache/cache_element.ts b/lib/utils/lru_cache/cache_element.ts deleted file mode 100644 index c286aab7a..000000000 --- a/lib/utils/lru_cache/cache_element.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * CacheElement represents an individual generic item within the LRUCache - */ -export class CacheElement<V> { - private _value: V | null; - private _time: number; - - get value(): V | null { - return this._value; - } - get time(): number { - return this._time; - } - - constructor(value: V | null = null) { - this._value = value; - this._time = Date.now(); - } - - public is_stale(timeout: number): boolean { - if (timeout <= 0) return false; - return Date.now() - this._time >= timeout; - } -} - -export default CacheElement; diff --git a/lib/utils/lru_cache/lru_cache.tests.ts b/lib/utils/lru_cache/lru_cache.tests.ts deleted file mode 100644 index 4c9de8d1a..000000000 --- a/lib/utils/lru_cache/lru_cache.tests.ts +++ /dev/null @@ -1,309 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assert } from 'chai'; -import { LRUCache } from './lru_cache'; -import { BrowserLRUCache } from './browser_lru_cache'; -import { ServerLRUCache } from './server_lru_cache'; - -const sleep = async (ms: number) => { - return await new Promise(r => setTimeout(r, ms)); -}; - -describe('/lib/core/odp/lru_cache (Default)', () => { - let cache: LRUCache<unknown, unknown>; - - describe('LRU Cache > Initialization', () => { - it('should successfully create a new cache with maxSize > 0 and timeout > 0', () => { - cache = new LRUCache({ - maxSize: 1000, - timeout: 2000, - }); - - assert.exists(cache); - - assert.equal(cache.maxSize, 1000); - assert.equal(cache.timeout, 2000); - }); - - it('should successfully create a new cache with maxSize == 0 and timeout == 0', () => { - cache = new LRUCache({ - maxSize: 0, - timeout: 0, - }); - - assert.exists(cache); - - assert.equal(cache.maxSize, 0); - assert.equal(cache.timeout, 0); - }); - }); - - describe('LRU Cache > Save & Lookup', () => { - const maxCacheSize = 2; - - beforeEach(() => { - cache = new LRUCache({ - maxSize: maxCacheSize, - timeout: 1000, - }); - }); - - it('should have no values in the cache upon initialization', () => { - assert.isNull(cache.peek(1)); - }); - - it('should save keys and values of any valid type', () => { - cache.save({ key: 'a', value: 1 }); // { a: 1 } - assert.equal(cache.peek('a'), 1); - - cache.save({ key: 2, value: 'b' }); // { a: 1, 2: 'b' } - assert.equal(cache.peek(2), 'b'); - - const foo = Symbol('foo'); - const bar = {}; - cache.save({ key: foo, value: bar }); // { 2: 'b', Symbol('foo'): {} } - assert.deepEqual({}, cache.peek(foo)); - }); - - it('should save values up to its maxSize', () => { - cache.save({ key: 'a', value: 1 }); // { a: 1 } - assert.equal(cache.peek('a'), 1); - - cache.save({ key: 'b', value: 2 }); // { a: 1, b: 2 } - assert.equal(cache.peek('a'), 1); - assert.equal(cache.peek('b'), 2); - - cache.save({ key: 'c', value: 3 }); // { b: 2, c: 3 } - assert.equal(cache.peek('a'), null); - assert.equal(cache.peek('b'), 2); - assert.equal(cache.peek('c'), 3); - }); - - it('should override values of matching keys when saving', () => { - cache.save({ key: 'a', value: 1 }); // { a: 1 } - assert.equal(cache.peek('a'), 1); - - cache.save({ key: 'a', value: 2 }); // { a: 2 } - assert.equal(cache.peek('a'), 2); - - cache.save({ key: 'a', value: 3 }); // { a: 3 } - assert.equal(cache.peek('a'), 3); - }); - - it('should update cache accordingly when using lookup/peek', () => { - assert.isNull(cache.lookup(3)); - - cache.save({ key: 'b', value: 201 }); // { b: 201 } - cache.save({ key: 'a', value: 101 }); // { b: 201, a: 101 } - - assert.equal(cache.lookup('b'), 201); // { a: 101, b: 201 } - - cache.save({ key: 'c', value: 302 }); // { b: 201, c: 302 } - - assert.isNull(cache.peek(1)); - assert.equal(cache.peek('b'), 201); - assert.equal(cache.peek('c'), 302); - assert.equal(cache.lookup('c'), 302); // { b: 201, c: 302 } - - cache.save({ key: 'a', value: 103 }); // { c: 302, a: 103 } - assert.equal(cache.peek('a'), 103); - assert.isNull(cache.peek('b')); - assert.equal(cache.peek('c'), 302); - }); - }); - - describe('LRU Cache > Size', () => { - it('should keep LRU Cache map size capped at cache.capacity', () => { - const maxCacheSize = 2; - - cache = new LRUCache({ - maxSize: maxCacheSize, - timeout: 1000, - }); - - cache.save({ key: 'a', value: 1 }); // { a: 1 } - cache.save({ key: 'b', value: 2 }); // { a: 1, b: 2 } - - assert.equal(cache.map.size, maxCacheSize); - assert.equal(cache.map.size, cache.maxSize); - }); - - it('should not save to cache if maxSize is 0', () => { - cache = new LRUCache({ - maxSize: 0, - timeout: 1000, - }); - - assert.isNull(cache.lookup('a')); - cache.save({ key: 'a', value: 100 }); - assert.isNull(cache.lookup('a')); - }); - - it('should not save to cache if maxSize is negative', () => { - cache = new LRUCache({ - maxSize: -500, - timeout: 1000, - }); - - assert.isNull(cache.lookup('a')); - cache.save({ key: 'a', value: 100 }); - assert.isNull(cache.lookup('a')); - }); - }); - - describe('LRU Cache > Timeout', () => { - it('should discard stale entries in the cache on peek/lookup when timeout is greater than 0', async () => { - const maxTimeout = 100; - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout, - }); - - cache.save({ key: 'a', value: 100 }); // { a: 100 } - cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } - cache.save({ key: 'c', value: 300 }); // { a: 100, b: 200, c: 300 } - - assert.equal(cache.peek('a'), 100); - assert.equal(cache.peek('b'), 200); - assert.equal(cache.peek('c'), 300); - - await sleep(150); - - assert.isNull(cache.lookup('a')); - assert.isNull(cache.lookup('b')); - assert.isNull(cache.lookup('c')); - - cache.save({ key: 'd', value: 400 }); // { d: 400 } - cache.save({ key: 'a', value: 101 }); // { d: 400, a: 101 } - - assert.equal(cache.lookup('a'), 101); // { d: 400, a: 101 } - assert.equal(cache.lookup('d'), 400); // { a: 101, d: 400 } - }); - - it('should never have stale entries if timeout is 0', async () => { - const maxTimeout = 0; - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout, - }); - - cache.save({ key: 'a', value: 100 }); // { a: 100 } - cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } - - await sleep(100); - assert.equal(cache.lookup('a'), 100); - assert.equal(cache.lookup('b'), 200); - }); - - it('should never have stale entries if timeout is less than 0', async () => { - const maxTimeout = -500; - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout, - }); - - cache.save({ key: 'a', value: 100 }); // { a: 100 } - cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } - - await sleep(100); - assert.equal(cache.lookup('a'), 100); - assert.equal(cache.lookup('b'), 200); - }); - }); - - describe('LRU Cache > Reset', () => { - it('should be able to reset the cache', async () => { - cache = new LRUCache({ maxSize: 2, timeout: 100 }); - cache.save({ key: 'a', value: 100 }); // { a: 100 } - cache.save({ key: 'b', value: 200 }); // { a: 100, b: 200 } - - await sleep(0); - - assert.equal(cache.map.size, 2); - cache.reset(); // { } - - await sleep(150); - - assert.equal(cache.map.size, 0); - - it('should be fully functional after resetting the cache', () => { - cache.save({ key: 'c', value: 300 }); // { c: 300 } - cache.save({ key: 'd', value: 400 }); // { c: 300, d: 400 } - assert.isNull(cache.peek('b')); - assert.equal(cache.peek('c'), 300); - assert.equal(cache.peek('d'), 400); - - cache.save({ key: 'a', value: 500 }); // { d: 400, a: 500 } - cache.save({ key: 'b', value: 600 }); // { a: 500, b: 600 } - assert.isNull(cache.peek('c')); - assert.equal(cache.peek('a'), 500); - assert.equal(cache.peek('b'), 600); - - const _ = cache.lookup('a'); // { b: 600, a: 500 } - assert.equal(500, _); - - cache.save({ key: 'c', value: 700 }); // { a: 500, c: 700 } - assert.isNull(cache.peek('b')); - assert.equal(cache.peek('a'), 500); - assert.equal(cache.peek('c'), 700); - }); - }); - }); -}); - -describe('/lib/core/odp/lru_cache (Client)', () => { - let cache: BrowserLRUCache<unknown, unknown>; - - it('should create and test the default client LRU Cache', () => { - cache = new BrowserLRUCache(); - assert.exists(cache); - assert.isNull(cache.lookup('a')); - assert.equal(cache.maxSize, 100); - assert.equal(cache.timeout, 600 * 1000); - - cache.save({ key: 'a', value: 100 }); - cache.save({ key: 'b', value: 200 }); - cache.save({ key: 'c', value: 300 }); - assert.equal(cache.map.size, 3); - assert.equal(cache.peek('a'), 100); - assert.equal(cache.lookup('b'), 200); - assert.deepEqual(cache.map.keys().next().value, 'a'); - }); -}); - -describe('/lib/core/odp/lru_cache (Server)', () => { - let cache: ServerLRUCache<unknown, unknown>; - - it('should create and test the default server LRU Cache', () => { - cache = new ServerLRUCache(); - assert.exists(cache); - assert.isNull(cache.lookup('a')); - assert.equal(cache.maxSize, 10000); - assert.equal(cache.timeout, 600 * 1000); - - cache.save({ key: 'a', value: 100 }); - cache.save({ key: 'b', value: 200 }); - cache.save({ key: 'c', value: 300 }); - assert.equal(cache.map.size, 3); - assert.equal(cache.peek('a'), 100); - assert.equal(cache.lookup('b'), 200); - assert.deepEqual(cache.map.keys().next().value, 'a'); - }); -}); diff --git a/lib/utils/lru_cache/lru_cache.ts b/lib/utils/lru_cache/lru_cache.ts deleted file mode 100644 index 0e8be1d8c..000000000 --- a/lib/utils/lru_cache/lru_cache.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright 2022-2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '../../modules/logging'; -import CacheElement from './cache_element'; - -export interface LRUCacheConfig { - maxSize: number; - timeout: number; -} - -export interface ICache<K, V> { - lookup(key: K): V | null; - save({ key, value }: { key: K; value: V }): void; - reset(): void; -} - -/** - * Least-Recently Used Cache (LRU Cache) Implementation with Generic Key-Value Pairs - * Analogous to a Map that has a specified max size and a timeout per element. - * - Removes the least-recently used element from the cache if max size exceeded. - * - Removes stale elements (entries older than their timeout) from the cache. - */ -export class LRUCache<K, V> implements ICache<K, V> { - private _map: Map<K, CacheElement<V>> = new Map(); - private _maxSize; // Defines maximum size of _map - private _timeout; // Milliseconds each entry has before it becomes stale - - get map(): Map<K, CacheElement<V>> { - return this._map; - } - - get maxSize(): number { - return this._maxSize; - } - - get timeout(): number { - return this._timeout; - } - - constructor({ maxSize, timeout }: LRUCacheConfig) { - const logger = getLogger(); - - logger.debug(`Provisioning cache with maxSize of ${maxSize}`); - logger.debug(`Provisioning cache with timeout of ${timeout}`); - - this._maxSize = maxSize; - this._timeout = timeout; - } - - /** - * Returns a valid, non-stale value from LRU Cache based on an input key. - * Additionally moves the element to the end of the cache and removes from cache if stale. - */ - lookup(key: K): V | null { - if (this._maxSize <= 0) { - return null; - } - - const element: CacheElement<V> | undefined = this._map.get(key); - - if (!element) return null; - - if (element.is_stale(this._timeout)) { - this._map.delete(key); - return null; - } - - this._map.delete(key); - this._map.set(key, element); - - return element.value; - } - - /** - * Inserts/moves an input key-value pair to the end of the LRU Cache. - * Removes the least-recently used element if the cache exceeds it's maxSize. - */ - save({ key, value }: { key: K; value: V }): void { - if (this._maxSize <= 0) return; - - const element: CacheElement<V> | undefined = this._map.get(key); - if (element) this._map.delete(key); - this._map.set(key, new CacheElement(value)); - - if (this._map.size > this._maxSize) { - const firstMapEntryKey = this._map.keys().next().value; - this._map.delete(firstMapEntryKey); - } - } - - /** - * Clears the LRU Cache - */ - reset(): void { - if (this._maxSize <= 0) return; - - this._map.clear(); - } - - /** - * Reads value from specified key without moving elements in the LRU Cache. - * @param {K} key - */ - peek(key: K): V | null { - if (this._maxSize <= 0) return null; - - const element: CacheElement<V> | undefined = this._map.get(key); - - return element?.value ?? null; - } -} - -export interface ISegmentsCacheConfig { - DEFAULT_CAPACITY: number; - DEFAULT_TIMEOUT_SECS: number; -} - -export default LRUCache; diff --git a/lib/utils/lru_cache/server_lru_cache.ts b/lib/utils/lru_cache/server_lru_cache.ts deleted file mode 100644 index 110d9b28e..000000000 --- a/lib/utils/lru_cache/server_lru_cache.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2022-2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import LRUCache, { ISegmentsCacheConfig } from './lru_cache'; - -export interface ServerLRUCacheConfig { - maxSize?: number; - timeout?: number; -} - -export const ServerLRUCacheConfig: ISegmentsCacheConfig = { - DEFAULT_CAPACITY: 10000, - DEFAULT_TIMEOUT_SECS: 600, -}; - -export class ServerLRUCache<K, V> extends LRUCache<K, V> { - constructor(config?: ServerLRUCacheConfig) { - super({ - maxSize: config?.maxSize?? ServerLRUCacheConfig.DEFAULT_CAPACITY, - timeout: config?.timeout?? ServerLRUCacheConfig.DEFAULT_TIMEOUT_SECS * 1000, - }); - } -} diff --git a/lib/utils/repeater/repeater.ts b/lib/utils/repeater/repeater.ts index 1425db431..9f307ab95 100644 --- a/lib/utils/repeater/repeater.ts +++ b/lib/utils/repeater/repeater.ts @@ -31,6 +31,7 @@ export interface Repeater { stop(): void; reset(): void; setTask(task: AsyncTransformer<number, unknown>): void; + isRunning(): boolean; } export interface BackoffController { @@ -74,13 +75,17 @@ export class IntervalRepeater implements Repeater { private interval: number; private failureCount = 0; private backoffController?: BackoffController; - private isRunning = false; + private running = false; constructor(interval: number, backoffController?: BackoffController) { this.interval = interval; this.backoffController = backoffController; } + isRunning(): boolean { + return this.running; + } + private handleSuccess() { this.failureCount = 0; this.backoffController?.reset(); @@ -94,7 +99,7 @@ export class IntervalRepeater implements Repeater { } private setTimer(timeout: number) { - if (!this.isRunning){ + if (!this.running){ return; } this.timeoutId = setTimeout(this.executeTask.bind(this), timeout); @@ -111,7 +116,7 @@ export class IntervalRepeater implements Repeater { } start(immediateExecution?: boolean): void { - this.isRunning = true; + this.running = true; if(immediateExecution) { scheduleMicrotask(this.executeTask.bind(this)); } else { @@ -120,7 +125,7 @@ export class IntervalRepeater implements Repeater { } stop(): void { - this.isRunning = false; + this.running = false; clearInterval(this.timeoutId); } diff --git a/lib/vuid/vuid.spec.ts b/lib/vuid/vuid.spec.ts new file mode 100644 index 000000000..0a0790b59 --- /dev/null +++ b/lib/vuid/vuid.spec.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; + +import { isVuid, makeVuid, VUID_MAX_LENGTH } from './vuid'; + +describe('isVuid', () => { + it('should return true if and only if the value strats with the VUID_PREFIX and is longer than vuid_prefix', () => { + expect(isVuid('vuid_a')).toBe(true); + expect(isVuid('vuid_123')).toBe(true); + expect(isVuid('vuid_')).toBe(false); + expect(isVuid('vuid')).toBe(false); + expect(isVuid('vui')).toBe(false); + expect(isVuid('vu_123')).toBe(false); + expect(isVuid('123')).toBe(false); + }) +}); + +describe('makeVuid', () => { + it('should return a string that is a valid vuid and whose length is within VUID_MAX_LENGTH', () => { + const vuid = makeVuid(); + expect(isVuid(vuid)).toBe(true); + expect(vuid.length).toBeLessThanOrEqual(VUID_MAX_LENGTH); + }); +}); diff --git a/lib/vuid/vuid.ts b/lib/vuid/vuid.ts new file mode 100644 index 000000000..d335c329d --- /dev/null +++ b/lib/vuid/vuid.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidV4 } from 'uuid'; + +export const VUID_PREFIX = `vuid_`; +export const VUID_MAX_LENGTH = 32; + +export const isVuid = (vuid: string): boolean => vuid.startsWith(VUID_PREFIX) && vuid.length > VUID_PREFIX.length; + +export const makeVuid = (): string => { + // make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details. + const uuid = uuidV4(); + const formatted = uuid.replace(/-/g, ''); + const vuidFull = `${VUID_PREFIX}${formatted}`; + + return vuidFull.length <= VUID_MAX_LENGTH ? vuidFull : vuidFull.substring(0, VUID_MAX_LENGTH); +}; diff --git a/lib/vuid/vuid_manager.spec.ts b/lib/vuid/vuid_manager.spec.ts new file mode 100644 index 000000000..5a4713d68 --- /dev/null +++ b/lib/vuid/vuid_manager.spec.ts @@ -0,0 +1,230 @@ +/** + * Copyright 2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; + +import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; + +import { getMockAsyncCache } from '../tests/mock/mock_cache'; +import { isVuid } from './vuid'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { exhaustMicrotasks } from '../tests/testUtils'; +import { get } from 'http'; + +const vuidCacheKey = 'optimizely-vuid'; + +describe('VuidCacheManager', () => { + it('should remove vuid from cache', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set(vuidCacheKey, 'vuid_valid'); + + const manager = new VuidCacheManager(cache); + await manager.remove(); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBeUndefined(); + }); + + it('should create and save a new vuid if there is no vuid in cache', async () => { + const cache = getMockAsyncCache<string>(); + + const manager = new VuidCacheManager(cache); + const vuid = await manager.load(); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe(vuid); + expect(isVuid(vuid!)).toBe(true); + }); + + it('should create and save a new vuid if old VUID from cache is not valid', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set(vuidCacheKey, 'invalid-vuid'); + + const manager = new VuidCacheManager(cache); + const vuid = await manager.load(); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe(vuid); + expect(isVuid(vuid!)).toBe(true); + }); + + it('should return the same vuid without modifying the cache after creating a new vuid', async () => { + const cache = getMockAsyncCache<string>(); + + const manager = new VuidCacheManager(cache); + const vuid1 = await manager.load(); + const vuid2 = await manager.load(); + expect(vuid1).toBe(vuid2); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe(vuid1); + }); + + it('should use the vuid in cache if available', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set(vuidCacheKey, 'vuid_valid'); + + const manager = new VuidCacheManager(cache); + const vuid1 = await manager.load(); + const vuid2 = await manager.load(); + expect(vuid1).toBe('vuid_valid'); + expect(vuid2).toBe('vuid_valid'); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe('vuid_valid'); + }); + + it('should use the new cache after setCache is called', async () => { + const cache1 = getMockAsyncCache<string>(); + const cache2 = getMockAsyncCache<string>(); + + await cache1.set(vuidCacheKey, 'vuid_123'); + await cache2.set(vuidCacheKey, 'vuid_456'); + + const manager = new VuidCacheManager(cache1); + const vuid1 = await manager.load(); + expect(vuid1).toBe('vuid_123'); + + manager.setCache(cache2); + await manager.load(); + const vuid2 = await cache2.get(vuidCacheKey); + expect(vuid2).toBe('vuid_456'); + + await manager.remove(); + const vuidInCache = await cache2.get(vuidCacheKey); + expect(vuidInCache).toBeUndefined(); + }); + + it('should sequence remove and load calls', async() => { + const cache = getMockAsyncCache<string>(); + const removeSpy = vi.spyOn(cache, 'remove'); + const getSpy = vi.spyOn(cache, 'get'); + const setSpy = vi.spyOn(cache, 'set'); + + const removePromise = resolvablePromise(); + removeSpy.mockReturnValueOnce(removePromise.promise); + + const getPromise = resolvablePromise<string>(); + getSpy.mockReturnValueOnce(getPromise.promise); + + const setPromise = resolvablePromise(); + setSpy.mockReturnValueOnce(setPromise.promise); + + const manager = new VuidCacheManager(cache); + + // this should try to remove from cache, which should stay pending + const call1 = manager.remove(); + + // this should try to get the vuid from cache + const call2 = manager.load(); + + // this should again try to remove vuid + const call3 = manager.remove(); + + await exhaustMicrotasks(); + + expect(removeSpy).toHaveBeenCalledTimes(1); // from the first manager.remove call + expect(getSpy).not.toHaveBeenCalled(); + + // this will resolve the first manager.remove call + removePromise.resolve(true); + await exhaustMicrotasks(); + await expect(call1).resolves.not.toThrow(); + + // this get call is from the load call + expect(getSpy).toHaveBeenCalledTimes(1); + await exhaustMicrotasks(); + + // as the get call is pending, remove call from the second manager.remove call should not yet happen + expect(removeSpy).toHaveBeenCalledTimes(1); + + // this should fail the load call, allowing the second remnove call to proceed + getPromise.reject(new Error('get failed')); + await exhaustMicrotasks(); + await expect(call2).rejects.toThrow(); + + expect(removeSpy).toHaveBeenCalledTimes(2); + }); +}); + +describe('DefaultVuidManager', () => { + const getMockCacheManager = () => ({ + remove: vi.fn(), + load: vi.fn(), + setCache: vi.fn(), + }); + + it('should return undefined for getVuid() before initialization', async () => { + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: getMockCacheManager() as unknown as VuidCacheManager, + enableVuid: true + }); + + expect(manager.getVuid()).toBeUndefined(); + }); + + it('should set the cache on vuidCacheManager', async () => { + const vuidCacheManager = getMockCacheManager(); + + const cache = getMockAsyncCache<string>(); + + const manager = new DefaultVuidManager({ + vuidCache: cache, + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: false + }); + + await manager.initialize(); + expect(vuidCacheManager.setCache).toHaveBeenCalledWith(cache); + }); + + it('should call remove on VuidCacheManager if enableVuid is false', async () => { + const vuidCacheManager = getMockCacheManager(); + + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: false + }); + + await manager.initialize(); + expect(vuidCacheManager.remove).toHaveBeenCalled(); + }); + + it('should return undefined for getVuid() after initialization if enableVuid is false', async () => { + const vuidCacheManager = getMockCacheManager(); + + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: false + }); + + await manager.initialize(); + expect(manager.getVuid()).toBeUndefined(); + }); + + it('should load vuid using VuidCacheManger if enableVuid=true', async () => { + const vuidCacheManager = getMockCacheManager(); + vuidCacheManager.load.mockResolvedValue('vuid_valid'); + + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: true + }); + + await manager.initialize(); + expect(vuidCacheManager.load).toHaveBeenCalled(); + expect(manager.getVuid()).toBe('vuid_valid'); + }); +}); diff --git a/lib/vuid/vuid_manager.ts b/lib/vuid/vuid_manager.ts new file mode 100644 index 000000000..8de680609 --- /dev/null +++ b/lib/vuid/vuid_manager.ts @@ -0,0 +1,132 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from '../modules/logging'; +import { Cache } from '../utils/cache/cache'; +import { AsyncProducer, Maybe } from '../utils/type'; +import { isVuid, makeVuid } from './vuid'; + +export interface VuidManager { + getVuid(): Maybe<string>; + isVuidEnabled(): boolean; + initialize(): Promise<void>; +} + +export class VuidCacheManager { + private logger?: LoggerFacade; + private vuidCacheKey = 'optimizely-vuid'; + private cache?: Cache<string>; + // if this value is not undefined, this means the same value is in the cache. + // if this is undefined, it could either mean that there is no value in the cache + // or that there is a value in the cache but it has not been loaded yet or failed + // to load. + private vuid?: string; + private waitPromise: Promise<unknown> = Promise.resolve(); + + constructor(cache?: Cache<string>, logger?: LoggerFacade) { + this.cache = cache; + this.logger = logger; + } + + setCache(cache: Cache<string>): void { + this.cache = cache; + this.vuid = undefined; + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + private async serialize<T>(fn: AsyncProducer<T>): Promise<T> { + const resultPromise = this.waitPromise.then(fn, fn); + this.waitPromise = resultPromise.catch(() => {}); + return resultPromise; + } + + async remove(): Promise<unknown> { + const removeFn = async () => { + if (!this.cache) { + return; + } + this.vuid = undefined; + await this.cache.remove(this.vuidCacheKey); + } + + return this.serialize(removeFn); + } + + async load(): Promise<Maybe<string>> { + if (this.vuid) { + return this.vuid; + } + + const loadFn = async () => { + if (!this.cache) { + return; + } + const cachedValue = await this.cache.get(this.vuidCacheKey); + if (cachedValue && isVuid(cachedValue)) { + this.vuid = cachedValue; + return this.vuid; + } + const newVuid = makeVuid(); + await this.cache.set(this.vuidCacheKey, newVuid); + this.vuid = newVuid; + return newVuid; + } + return this.serialize(loadFn); + } +} + +export type VuidManagerConfig = { + enableVuid?: boolean; + vuidCache: Cache<string>; + vuidCacheManager: VuidCacheManager; +} + +export class DefaultVuidManager implements VuidManager { + private vuidCacheManager: VuidCacheManager; + private vuid?: string; + private vuidCache: Cache<string>; + private vuidEnabled = false; + + constructor(config: VuidManagerConfig) { + this.vuidCacheManager = config.vuidCacheManager; + this.vuidEnabled = config.enableVuid || false; + this.vuidCache = config.vuidCache; + } + + getVuid(): Maybe<string> { + return this.vuid; + } + + isVuidEnabled(): boolean { + return this.vuidEnabled; + } + + /** + * initializes the VuidManager + * @returns Promise that resolves when the VuidManager is initialized + */ + async initialize(): Promise<void> { + this.vuidCacheManager.setCache(this.vuidCache); + if (!this.vuidEnabled) { + await this.vuidCacheManager.remove(); + return; + } + + this.vuid = await this.vuidCacheManager.load(); + } +} diff --git a/lib/vuid/vuid_manager_factory.browser.spec.ts b/lib/vuid/vuid_manager_factory.browser.spec.ts new file mode 100644 index 000000000..d4a7c2c72 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.browser.spec.ts @@ -0,0 +1,84 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, expect, it, beforeEach } from 'vitest'; + +vi.mock('../utils/cache/local_storage_cache.browser', () => { + return { + LocalStorageCache: vi.fn(), + }; +}); + +vi.mock('./vuid_manager', () => { + return { + DefaultVuidManager: vi.fn(), + VuidCacheManager: vi.fn(), + }; +}); + +import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { createVuidManager } from './vuid_manager_factory.browser'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; + +describe('createVuidManager', () => { + const MockVuidCacheManager = vi.mocked(VuidCacheManager); + const MockLocalStorageCache = vi.mocked(LocalStorageCache); + const MockDefaultVuidManager = vi.mocked(DefaultVuidManager); + + beforeEach(() => { + MockLocalStorageCache.mockClear(); + MockDefaultVuidManager.mockClear(); + }); + + it('should pass the enableVuid option to the DefaultVuidManager', () => { + const manager = createVuidManager({ enableVuid: true }); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].enableVuid).toBe(true); + + const manager2 = createVuidManager({ enableVuid: false }); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockDefaultVuidManager.mock.calls[1][0].enableVuid).toBe(false); + }); + + it('should use the provided cache', () => { + const cache = getMockSyncCache<string>(); + const manager = createVuidManager({ enableVuid: true, vuidCache: cache }); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].vuidCache).toBe(cache); + }); + + it('should use a LocalStorageCache if no cache is provided', () => { + const manager = createVuidManager({ enableVuid: true }); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + + const usedCache = MockDefaultVuidManager.mock.calls[0][0].vuidCache; + expect(usedCache).toBe(MockLocalStorageCache.mock.instances[0]); + }); + + it('should use a single VuidCacheManager instance for all VuidManager instances', () => { + const manager1 = createVuidManager({ enableVuid: true }); + const manager2 = createVuidManager({ enableVuid: true }); + expect(manager1).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockVuidCacheManager.mock.instances.length).toBe(1); + + const usedCacheManager1 = MockDefaultVuidManager.mock.calls[0][0].vuidCacheManager; + const usedCacheManager2 = MockDefaultVuidManager.mock.calls[1][0].vuidCacheManager; + expect(usedCacheManager1).toBe(usedCacheManager2); + expect(usedCacheManager1).toBe(MockVuidCacheManager.mock.instances[0]); + }); +}); diff --git a/lib/vuid/vuid_manager_factory.browser.ts b/lib/vuid/vuid_manager_factory.browser.ts new file mode 100644 index 000000000..cf8df6a44 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.browser.ts @@ -0,0 +1,28 @@ +/** +* Copyright 2024, Optimizely +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { DefaultVuidManager, VuidCacheManager, VuidManager } from './vuid_manager'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { VuidManagerOptions } from './vuid_manager_factory'; + +export const vuidCacheManager = new VuidCacheManager(); + +export const createVuidManager = (options: VuidManagerOptions): VuidManager => { + return new DefaultVuidManager({ + vuidCacheManager, + vuidCache: options.vuidCache || new LocalStorageCache<string>(), + enableVuid: options.enableVuid + }); +} diff --git a/lib/odp/odp_utils.ts b/lib/vuid/vuid_manager_factory.node.spec.ts similarity index 51% rename from lib/odp/odp_utils.ts rename to lib/vuid/vuid_manager_factory.node.spec.ts index 875b7e091..2a81f9a8a 100644 --- a/lib/odp/odp_utils.ts +++ b/lib/vuid/vuid_manager_factory.node.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,13 @@ * limitations under the License. */ -/** - * Validate event data value types - * @param data Event data to be validated - * @returns True if an invalid type was found in the data otherwise False - * @private - */ -export function invalidOdpDataFound(data: Map<string, any>): boolean { - const validTypes: string[] = ['string', 'number', 'boolean']; - let foundInvalidValue = false; - data.forEach(value => { - if (!validTypes.includes(typeof value) && value !== null) { - foundInvalidValue = true; - } +import { vi, describe, expect, it } from 'vitest'; + +import { createVuidManager } from './vuid_manager_factory.node'; + +describe('createVuidManager', () => { + it('should throw an error', () => { + expect(() => createVuidManager({ enableVuid: true })) + .toThrowError('VUID is not supported in Node.js environment'); }); - return foundInvalidValue; -} +}); diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts new file mode 100644 index 000000000..993fbb60a --- /dev/null +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -0,0 +1,22 @@ +/** +* Copyright 2024, Optimizely +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { VuidManager } from './vuid_manager'; +import { VuidManagerOptions } from './vuid_manager_factory'; + +export const createVuidManager = (options: VuidManagerOptions): VuidManager => { + throw new Error('VUID is not supported in Node.js environment'); +}; + diff --git a/lib/vuid/vuid_manager_factory.react_native.spec.ts b/lib/vuid/vuid_manager_factory.react_native.spec.ts new file mode 100644 index 000000000..22920c099 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.react_native.spec.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, expect, it, beforeEach } from 'vitest'; + +vi.mock('../utils/cache/async_storage_cache.react_native', () => { + return { + AsyncStorageCache: vi.fn(), + }; +}); + +vi.mock('./vuid_manager', () => { + return { + DefaultVuidManager: vi.fn(), + VuidCacheManager: vi.fn(), + }; +}); + +import { getMockAsyncCache } from '../tests/mock/mock_cache'; +import { createVuidManager } from './vuid_manager_factory.react_native'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; + +import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; + +describe('createVuidManager', () => { + const MockVuidCacheManager = vi.mocked(VuidCacheManager); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); + const MockDefaultVuidManager = vi.mocked(DefaultVuidManager); + + beforeEach(() => { + MockAsyncStorageCache.mockClear(); + MockDefaultVuidManager.mockClear(); + }); + + it('should pass the enableVuid option to the DefaultVuidManager', () => { + const manager = createVuidManager({ enableVuid: true }); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].enableVuid).toBe(true); + + const manager2 = createVuidManager({ enableVuid: false }); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockDefaultVuidManager.mock.calls[1][0].enableVuid).toBe(false); + }); + + it('should use the provided cache', () => { + const cache = getMockAsyncCache<string>(); + const manager = createVuidManager({ enableVuid: true, vuidCache: cache }); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].vuidCache).toBe(cache); + }); + + it('should use a AsyncStorageCache if no cache is provided', () => { + const manager = createVuidManager({ enableVuid: true }); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + + const usedCache = MockDefaultVuidManager.mock.calls[0][0].vuidCache; + expect(usedCache).toBe(MockAsyncStorageCache.mock.instances[0]); + }); + + it('should use a single VuidCacheManager instance for all VuidManager instances', () => { + const manager1 = createVuidManager({ enableVuid: true }); + const manager2 = createVuidManager({ enableVuid: true }); + expect(manager1).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockVuidCacheManager.mock.instances.length).toBe(1); + + const usedCacheManager1 = MockDefaultVuidManager.mock.calls[0][0].vuidCacheManager; + const usedCacheManager2 = MockDefaultVuidManager.mock.calls[1][0].vuidCacheManager; + expect(usedCacheManager1).toBe(usedCacheManager2); + expect(usedCacheManager1).toBe(MockVuidCacheManager.mock.instances[0]); + }); +}); diff --git a/lib/vuid/vuid_manager_factory.react_native.ts b/lib/vuid/vuid_manager_factory.react_native.ts new file mode 100644 index 000000000..6eba4c9f2 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.react_native.ts @@ -0,0 +1,28 @@ +/** +* Copyright 2024, Optimizely +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { DefaultVuidManager, VuidCacheManager, VuidManager } from './vuid_manager'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { VuidManagerOptions } from './vuid_manager_factory'; + +export const vuidCacheManager = new VuidCacheManager(); + +export const createVuidManager = (options: VuidManagerOptions): VuidManager => { + return new DefaultVuidManager({ + vuidCacheManager, + vuidCache: options.vuidCache || new AsyncStorageCache<string>(), + enableVuid: options.enableVuid + }); +} diff --git a/lib/utils/lru_cache/index.ts b/lib/vuid/vuid_manager_factory.ts similarity index 63% rename from lib/utils/lru_cache/index.ts rename to lib/vuid/vuid_manager_factory.ts index fb7ada423..ab2264242 100644 --- a/lib/utils/lru_cache/index.ts +++ b/lib/vuid/vuid_manager_factory.ts @@ -1,11 +1,11 @@ /** - * Copyright 2022, Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,8 +14,9 @@ * limitations under the License. */ -import { ICache, LRUCache } from './lru_cache'; -import { BrowserLRUCache } from './browser_lru_cache'; -import { ServerLRUCache } from './server_lru_cache'; +import { Cache } from '../utils/cache/cache'; -export { ICache, LRUCache, BrowserLRUCache, ServerLRUCache }; +export type VuidManagerOptions = { + vuidCache?: Cache<string>; + enableVuid?: boolean; +} diff --git a/tests/browserAsyncStorageCache.spec.ts b/tests/browserAsyncStorageCache.spec.ts deleted file mode 100644 index c30b675bc..000000000 --- a/tests/browserAsyncStorageCache.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, it, expect, vi } from 'vitest'; - -import BrowserAsyncStorageCache from '../lib/plugins/key_value_cache/browserAsyncStorageCache'; - -describe('BrowserAsyncStorageCache', () => { - const KEY_THAT_EXISTS = 'keyThatExists'; - const VALUE_FOR_KEY_THAT_EXISTS = 'some really super value that exists for keyThatExists'; - const NONEXISTENT_KEY = 'someKeyThatDoesNotExist'; - - let cacheInstance: BrowserAsyncStorageCache; - - beforeEach(() => { - const stubData = new Map<string, string>(); - stubData.set(KEY_THAT_EXISTS, VALUE_FOR_KEY_THAT_EXISTS); - - cacheInstance = new BrowserAsyncStorageCache(); - - vi - .spyOn(localStorage, 'getItem') - .mockImplementation((key) => key == KEY_THAT_EXISTS ? VALUE_FOR_KEY_THAT_EXISTS : null); - vi - .spyOn(localStorage, 'setItem') - .mockImplementation(() => 1); - vi - .spyOn(localStorage, 'removeItem') - .mockImplementation((key) => key == KEY_THAT_EXISTS); - }); - - describe('contains', () => { - it('should return true if value with key exists', async () => { - const keyWasFound = await cacheInstance.contains(KEY_THAT_EXISTS); - - expect(keyWasFound).toBe(true); - }); - - it('should return false if value with key does not exist', async () => { - const keyWasFound = await cacheInstance.contains(NONEXISTENT_KEY); - - expect(keyWasFound).toBe(false); - }); - }); - - describe('get', () => { - it('should return correct string when item is found in cache', async () => { - const foundValue = await cacheInstance.get(KEY_THAT_EXISTS); - - expect(foundValue).toEqual(VALUE_FOR_KEY_THAT_EXISTS); - }); - - it('should return undefined if item is not found in cache', async () => { - const json = await cacheInstance.get(NONEXISTENT_KEY); - - expect(json).toBeUndefined(); - }); - }); - - describe('remove', () => { - it('should return true after removing a found entry', async () => { - const wasSuccessful = await cacheInstance.remove(KEY_THAT_EXISTS); - - expect(wasSuccessful).toBe(true); - }); - - it('should return false after trying to remove an entry that is not found ', async () => { - const wasSuccessful = await cacheInstance.remove(NONEXISTENT_KEY); - - expect(wasSuccessful).toBe(false); - }); - }); - - describe('set', () => { - it('should resolve promise if item was successfully set in the cache', async () => { - await cacheInstance.set('newTestKey', 'a value for this newTestKey'); - }); - }); -}); diff --git a/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts deleted file mode 100644 index 07632c72a..000000000 --- a/tests/odpEventApiManager.spec.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; - -import { anyString, anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { NodeOdpEventApiManager } from '../lib/odp/event_manager/event_api_manager.node'; -import { OdpEvent } from '../lib/odp/event_manager/odp_event'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; -import { OdpConfig } from '../lib/odp/odp_config'; - -const data1 = new Map<string, unknown>(); -data1.set('key11', 'value-1'); -data1.set('key12', true); -data1.set('key12', 3.5); -data1.set('key14', null); -const data2 = new Map<string, unknown>(); -data2.set('key2', 'value-2'); -const ODP_EVENTS = [ - new OdpEvent('t1', 'a1', new Map([['id-key-1', 'id-value-1']]), data1), - new OdpEvent('t2', 'a2', new Map([['id-key-2', 'id-value-2']]), data2), -]; - -const API_KEY = 'test-api-key'; -const API_HOST = 'https://odp.example.com/'; -const PIXEL_URL = 'https://odp.pixel.com/'; - -const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); - -describe('NodeOdpEventApiManager', () => { - let mockLogger: LogHandler; - let mockRequestHandler: RequestHandler; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockRequestHandler = mock<RequestHandler>(); - }); - - beforeEach(() => { - resetCalls(mockLogger); - resetCalls(mockRequestHandler); - }); - - const managerInstance = () => { - const manager = new NodeOdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); - return manager; - } - - const abortableRequest = (statusCode: number, body: string) => { - return { - abort: () => {}, - responsePromise: Promise.resolve({ - statusCode, - body, - headers: {}, - }), - }; - }; - - it('should should send events successfully and not suggest retry', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, '') - ); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); - - expect(shouldRetry).toBe(false); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should not suggest a retry for 400 HTTP response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(400, '') - ); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); - - expect(shouldRetry).toBe(false); - verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (400)')).once(); - }); - - it('should suggest a retry for 500 HTTP response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(500, '') - ); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); - - expect(shouldRetry).toBe(true); - verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (500)')).once(); - }); - - it('should suggest a retry for network timeout', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ - abort: () => {}, - responsePromise: Promise.reject(new Error('Request timed out')), - }); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); - - expect(shouldRetry).toBe(true); - verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (Request timed out)')).once(); - }); - - it('should send events to the correct host using correct api key', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ - abort: () => {}, - responsePromise: Promise.reject(new Error('Request timed out')), - }); - - const manager = managerInstance(); - - await manager.sendEvents(odpConfig, ODP_EVENTS); - - verify(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).once(); - - const [initUrl, headers] = capture(mockRequestHandler.makeRequest).first(); - expect(initUrl).toEqual(`${API_HOST}/v3/events`); - expect(headers['x-api-key']).toEqual(odpConfig.apiKey); - }); -}); diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts deleted file mode 100644 index 38cf9d379..000000000 --- a/tests/odpEventManager.spec.ts +++ /dev/null @@ -1,733 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, beforeAll, it, vi, expect } from 'vitest'; - -import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE, ERROR_MESSAGES } from '../lib/utils/enums'; -import { OdpConfig } from '../lib/odp/odp_config'; -import { Status } from '../lib/odp/event_manager/odp_event_manager'; -import { BrowserOdpEventManager } from '../lib/odp/event_manager/event_manager.browser'; -import { NodeOdpEventManager } from '../lib/odp/event_manager/event_manager.node'; -import { OdpEventManager } from '../lib/odp/event_manager/odp_event_manager'; -import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; -import { IOdpEventApiManager } from '../lib/odp/event_manager/odp_event_api_manager'; -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { OdpEvent } from '../lib/odp/event_manager/odp_event'; -import { IUserAgentParser } from '../lib/odp/ua_parser/user_agent_parser'; -import { UserAgentInfo } from '../lib/odp/ua_parser/user_agent_info'; -import { resolve } from 'path'; -import { advanceTimersByTime } from './testUtils'; - -const API_KEY = 'test-api-key'; -const API_HOST = 'https://odp.example.com/'; -const PIXEL_URL = 'https://odp.pixel.com/'; -const MOCK_IDEMPOTENCE_ID = 'c1dc758e-f095-4f09-9b49-172d74c53880'; -const EVENTS: OdpEvent[] = [ - new OdpEvent( - 't1', - 'a1', - new Map([['id-key-1', 'id-value-1']]), - new Map<string, unknown>([ - ['key-1', 'value1'], - ['key-2', null], - ['key-3', 3.3], - ['key-4', true], - ]), - ), - new OdpEvent( - 't2', - 'a2', - new Map([['id-key-2', 'id-value-2']]), - new Map( - Object.entries({ - 'key-2': 'value2', - data_source: 'my-source', - }) - ) - ), -]; -// naming for object destructuring -const clientEngine = 'javascript-sdk'; -const clientVersion = '4.9.3'; -const PROCESSED_EVENTS: OdpEvent[] = [ - new OdpEvent( - 't1', - 'a1', - new Map([['id-key-1', 'id-value-1']]), - new Map( - Object.entries({ - idempotence_id: MOCK_IDEMPOTENCE_ID, - data_source_type: 'sdk', - data_source: clientEngine, - data_source_version: clientVersion, - 'key-1': 'value1', - 'key-2': null, - 'key-3': 3.3, - 'key-4': true, - }) - ) - ), - new OdpEvent( - 't2', - 'a2', - new Map([['id-key-2', 'id-value-2']]), - new Map( - Object.entries({ - idempotence_id: MOCK_IDEMPOTENCE_ID, - data_source_type: 'sdk', - data_source: clientEngine, - data_source_version: clientVersion, - 'key-2': 'value2', - }) - ) - ), -]; -const EVENT_WITH_EMPTY_IDENTIFIER = new OdpEvent( - 't4', - 'a4', - new Map(), - new Map<string, unknown>([ - ['key-53f3', true], - ['key-a04a', 123], - ['key-2ab4', 'Linus Torvalds'], - ]), -); -const EVENT_WITH_UNDEFINED_IDENTIFIER = new OdpEvent( - 't4', - 'a4', - undefined, - new Map<string, unknown>([ - ['key-53f3', false], - ['key-a04a', 456], - ['key-2ab4', 'Bill Gates'] - ]), -); -const makeEvent = (id: number) => { - const identifiers = new Map<string, string>(); - identifiers.set('identifier1', 'value1-' + id); - identifiers.set('identifier2', 'value2-' + id); - - const data = new Map<string, unknown>(); - data.set('data1', 'data-value1-' + id); - data.set('data2', id); - - return new OdpEvent('test-type-' + id, 'test-action-' + id, identifiers, data); -}; -const pause = (timeoutMilliseconds: number): Promise<void> => { - return new Promise(resolve => setTimeout(resolve, timeoutMilliseconds)); -}; -const abortableRequest = (statusCode: number, body: string) => { - return { - abort: () => {}, - responsePromise: Promise.resolve({ - statusCode, - body, - headers: {}, - }), - }; -}; - -class TestOdpEventManager extends OdpEventManager { - constructor(options: any) { - super(options); - } - protected initParams(batchSize: number, queueSize: number, flushInterval: number): void { - this.queueSize = queueSize; - this.batchSize = batchSize; - this.flushInterval = flushInterval; - } - protected discardEventsIfNeeded(): void { - } - protected hasNecessaryIdentifiers = (event: OdpEvent): boolean => event.identifiers.size >= 0; -} - -describe('OdpEventManager', () => { - let mockLogger: LogHandler; - let mockApiManager: IOdpEventApiManager; - - let odpConfig: OdpConfig; - let logger: LogHandler; - let apiManager: IOdpEventApiManager; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockApiManager = mock<IOdpEventApiManager>(); - odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); - logger = instance(mockLogger); - apiManager = instance(mockApiManager); - }); - - beforeEach(() => { - vi.useFakeTimers(); - resetCalls(mockLogger); - resetCalls(mockApiManager); - }); - - afterEach(() => { - vi.clearAllTimers(); - }); - - it('should log an error and not start if start() is called without a config', () => { - const eventManager = new TestOdpEventManager({ - odpConfig: undefined, - apiManager, - logger, - clientEngine, - clientVersion, - }); - - eventManager.start(); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).once(); - expect(eventManager.status).toEqual(Status.Stopped); - }); - - it('should start() correctly after odpConfig is provided', () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - - expect(eventManager.status).toEqual(Status.Stopped); - eventManager.updateSettings(odpConfig); - eventManager.start(); - expect(eventManager.status).toEqual(Status.Running); - }); - - it('should log and discard events when event manager is not running', () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - - expect(eventManager.status).toEqual(Status.Stopped); - eventManager.sendEvent(EVENTS[0]); - verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.')).once(); - expect(eventManager.getQueue().length).toEqual(0); - }); - - it('should discard events with invalid data', () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - eventManager.start(); - - expect(eventManager.status).toEqual(Status.Running); - - // make an event with invalid data key-value entry - const badEvent = new OdpEvent( - 't3', - 'a3', - new Map([['id-key-3', 'id-value-3']]), - new Map<string, unknown>([ - ['key-1', false], - ['key-2', { random: 'object', whichShouldFail: true }], - ]), - ); - eventManager.sendEvent(badEvent); - - verify(mockLogger.log(LogLevel.ERROR, 'Event data found to be invalid.')).once(); - expect(eventManager.getQueue().length).toEqual(0); - }); - - it('should log a max queue hit and discard ', () => { - // set queue to maximum of 1 - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - queueSize: 1, // With max queue size set to 1... - }); - - eventManager.start(); - - eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... - // ...try adding the second event - eventManager.sendEvent(EVENTS[1]); - - verify( - mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', 1) - ).once(); - }); - - it('should add additional information to each event', () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - eventManager.start(); - - const processedEventData = PROCESSED_EVENTS[0].data; - - const eventData = eventManager['augmentCommonData'](EVENTS[0].data); - - expect((eventData.get('idempotence_id') as string).length).toEqual( - (processedEventData.get('idempotence_id') as string).length - ); - expect(eventData.get('data_source_type')).toEqual(processedEventData.get('data_source_type')); - expect(eventData.get('data_source')).toEqual(processedEventData.get('data_source')); - expect(eventData.get('data_source_version')).toEqual(processedEventData.get('data_source_version')); - expect(eventData.get('key-1')).toEqual(processedEventData.get('key-1')); - expect(eventData.get('key-2')).toEqual(processedEventData.get('key-2')); - expect(eventData.get('key-3')).toEqual(processedEventData.get('key-3')); - expect(eventData.get('key-4')).toEqual(processedEventData.get('key-4')); - }); - - it('should attempt to flush an empty queue at flush intervals if batchSize is greater than 1', async () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 10, - flushInterval: 100, - }); - - //@ts-ignore - const processQueueSpy = vi.spyOn(eventManager, 'processQueue'); - - eventManager.start(); - // do not add events to the queue, but allow for... - vi.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) - - expect(processQueueSpy).toHaveBeenCalledTimes(3); - }); - - - it('should not flush periodically if batch size is 1', async () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 1, - flushInterval: 100, - }); - - //@ts-ignore - const processQueueSpy = vi.spyOn(eventManager, 'processQueue'); - - eventManager.start(); - eventManager.sendEvent(EVENTS[0]); - eventManager.sendEvent(EVENTS[1]); - - vi.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) - - expect(processQueueSpy).toHaveBeenCalledTimes(2); - }); - - it('should dispatch events in correct batch sizes', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); - - const apiManager = instance(mockApiManager); - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 10, // with batch size of 10... - flushInterval: 250, - }); - - eventManager.start(); - - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - - await Promise.resolve(); - - // as we are not advancing the vi fake timers, no flush should occur - // ...there should be 3 batches: - // batch #1 with 10, batch #2 with 10, and batch #3 (after flushInterval lapsed) with 5 = 25 events - verify(mockApiManager.sendEvents(anything(), anything())).twice(); - - // rest of the events should now be flushed - await advanceTimersByTime(250); - verify(mockApiManager.sendEvents(anything(), anything())).thrice(); - }); - - it('should dispatch events with correct payload', async () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 10, - flushInterval: 100, - }); - - eventManager.start(); - EVENTS.forEach(event => eventManager.sendEvent(event)); - - await advanceTimersByTime(100); - // sending 1 batch of 2 events after flushInterval since batchSize is 10 - verify(mockApiManager.sendEvents(anything(), anything())).once(); - const [_, events] = capture(mockApiManager.sendEvents).last(); - expect(events.length).toEqual(2); - expect(events[0].identifiers.size).toEqual(PROCESSED_EVENTS[0].identifiers.size); - expect(events[0].data.size).toEqual(PROCESSED_EVENTS[0].data.size); - expect(events[1].identifiers.size).toEqual(PROCESSED_EVENTS[1].identifiers.size); - expect(events[1].data.size).toEqual(PROCESSED_EVENTS[1].data.size); - }); - - it('should dispatch events with correct odpConfig', async () => { - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 10, - flushInterval: 100, - }); - - eventManager.start(); - EVENTS.forEach(event => eventManager.sendEvent(event)); - - await advanceTimersByTime(100); - - // sending 1 batch of 2 events after flushInterval since batchSize is 10 - verify(mockApiManager.sendEvents(anything(), anything())).once(); - const [usedOdpConfig] = capture(mockApiManager.sendEvents).last(); - expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); - }); - - it('should augment events with data from user agent parser', async () => { - const userAgentParser : IUserAgentParser = { - parseUserAgentInfo: function (): UserAgentInfo { - return { - os: { 'name': 'windows', 'version': '11' }, - device: { 'type': 'laptop', 'model': 'thinkpad' }, - } - } - } - - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 10, - flushInterval: 100, - userAgentParser, - }); - - eventManager.start(); - EVENTS.forEach(event => eventManager.sendEvent(event)); - await advanceTimersByTime(100); - - verify(mockApiManager.sendEvents(anything(), anything())).called(); - const [_, events] = capture(mockApiManager.sendEvents).last(); - const event = events[0]; - - expect(event.data.get('os')).toEqual('windows'); - expect(event.data.get('os_version')).toEqual('11'); - expect(event.data.get('device_type')).toEqual('laptop'); - expect(event.data.get('model')).toEqual('thinkpad'); - }); - - it('should retry failed events', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(true) - - const retries = 3; - const apiManager = instance(mockApiManager); - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 2, - flushInterval: 100, - retries, - }); - - eventManager.start(); - for (let i = 0; i < 4; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - - vi.runAllTicks(); - vi.useRealTimers(); - await pause(100); - - // retry 3x for 2 batches or 6 calls to attempt to process - verify(mockApiManager.sendEvents(anything(), anything())).times(6); - }); - - it('should flush all queued events when flush() is called', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); - - const apiManager = instance(mockApiManager); - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 200, - flushInterval: 100, - }); - - eventManager.start(); - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - - expect(eventManager.getQueue().length).toEqual(25); - - eventManager.flush(); - - await Promise.resolve(); - - verify(mockApiManager.sendEvents(anything(), anything())).once(); - expect(eventManager.getQueue().length).toEqual(0); - }); - - it('should flush all queued events before stopping', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); - const apiManager = instance(mockApiManager); - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 200, - flushInterval: 100, - }); - - eventManager.start(); - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - - expect(eventManager.getQueue().length).toEqual(25); - - eventManager.flush(); - - await Promise.resolve(); - - verify(mockApiManager.sendEvents(anything(), anything())).once(); - expect(eventManager.getQueue().length).toEqual(0); - }); - - it('should flush all queued events using the old odpConfig when updateSettings is called()', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); - - const odpConfig = new OdpConfig('old-key', 'old-host', 'https://new-odp.pixel.com/', []); - const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com/', []); - - const apiManager = instance(mockApiManager); - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 200, - flushInterval: 100, - }); - - eventManager.start(); - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - - expect(eventManager.getQueue().length).toEqual(25); - - eventManager.updateSettings(updatedConfig); - - await Promise.resolve(); - - verify(mockApiManager.sendEvents(anything(), anything())).once(); - expect(eventManager.getQueue().length).toEqual(0); - const [usedOdpConfig] = capture(mockApiManager.sendEvents).last(); - expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); - }); - - it('should use updated odpConfig to send events', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); - - const odpConfig = new OdpConfig('old-key', 'old-host', 'https://new-odp.pixel.com/', []); - const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com/', []); - - const apiManager = instance(mockApiManager); - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 200, - flushInterval: 100, - }); - - eventManager.start(); - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - - expect(eventManager.getQueue().length).toEqual(25); - - await advanceTimersByTime(100); - - expect(eventManager.getQueue().length).toEqual(0); - let [usedOdpConfig] = capture(mockApiManager.sendEvents).first(); - expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); - - eventManager.updateSettings(updatedConfig); - - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - - await advanceTimersByTime(100); - - expect(eventManager.getQueue().length).toEqual(0); - ([usedOdpConfig] = capture(mockApiManager.sendEvents).last()); - expect(usedOdpConfig.equals(updatedConfig)).toBeTruthy(); - }); - - it('should prepare correct payload for register VUID', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); - - const apiManager = instance(mockApiManager); - - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 10, - flushInterval: 250, - }); - - const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; - const fsUserId = 'test-fs-user-id'; - - eventManager.start(); - eventManager.registerVuid(vuid); - - await advanceTimersByTime(250); - - const [_, events] = capture(mockApiManager.sendEvents).last(); - expect(events.length).toBe(1); - - const [event] = events; - expect(event.type).toEqual('fullstack'); - expect(event.action).toEqual(ODP_EVENT_ACTION.INITIALIZED); - expect(event.identifiers).toEqual(new Map([['vuid', vuid]])); - expect((event.data.get("idempotence_id") as string).length).toBe(36); // uuid length - expect((event.data.get("data_source_type") as string)).toEqual('sdk'); - expect((event.data.get("data_source") as string)).toEqual('javascript-sdk'); - expect(event.data.get("data_source_version") as string).not.toBeNull(); - }); - - it('should send correct event payload for identify user', async () => { - when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); - - const apiManager = instance(mockApiManager); - - const eventManager = new TestOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - batchSize: 10, - flushInterval: 250, - }); - - const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; - const fsUserId = 'test-fs-user-id'; - - eventManager.start(); - eventManager.identifyUser(fsUserId, vuid); - - await advanceTimersByTime(260); - - const [_, events] = capture(mockApiManager.sendEvents).last(); - expect(events.length).toBe(1); - - const [event] = events; - expect(event.type).toEqual(ODP_DEFAULT_EVENT_TYPE); - expect(event.action).toEqual(ODP_EVENT_ACTION.IDENTIFIED); - expect(event.identifiers).toEqual(new Map([['vuid', vuid], ['fs_user_id', fsUserId]])); - expect((event.data.get("idempotence_id") as string).length).toBe(36); // uuid length - expect((event.data.get("data_source_type") as string)).toEqual('sdk'); - expect((event.data.get("data_source") as string)).toEqual('javascript-sdk'); - expect(event.data.get("data_source_version") as string).not.toBeNull(); - }); - - it('should error when no identifiers are provided in Node', () => { - const eventManager = new NodeOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - - eventManager.start(); - eventManager.sendEvent(EVENT_WITH_EMPTY_IDENTIFIER); - eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); - eventManager.stop(); - - vi.runAllTicks(); - - verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).twice(); - }); - - it('should never error when no identifiers are provided in Browser', () => { - const eventManager = new BrowserOdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - - eventManager.start(); - eventManager.sendEvent(EVENT_WITH_EMPTY_IDENTIFIER); - eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); - eventManager.stop(); - - vi.runAllTicks(); - - verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).never(); - }); -}); diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts deleted file mode 100644 index ee9415a78..000000000 --- a/tests/odpManager.browser.spec.ts +++ /dev/null @@ -1,513 +0,0 @@ -/** - * Copyright 2023-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; - -import { instance, mock, resetCalls } from 'ts-mockito'; - -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; -import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; - -import { BrowserOdpManager } from './../lib/odp/odp_manager.browser'; - -import { OdpConfig } from '../lib/odp/odp_config'; -import { BrowserOdpEventApiManager } from '../lib/odp/event_manager/event_api_manager.browser'; -import { OdpSegmentManager } from './../lib/odp/segment_manager/odp_segment_manager'; -import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; -import { VuidManager } from '../lib/plugins/vuid_manager'; -import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; -import { BrowserOdpEventManager } from '../lib/odp/event_manager/event_manager.browser'; -import { OdpOptions } from '../lib/shared_types'; - - -const keyA = 'key-a'; -const hostA = 'host-a'; -const pixelA = 'pixel-a'; -const segmentsA = ['a']; -const userA = 'fs-user-a'; -const vuidA = 'vuid_a'; -const odpConfigA = new OdpConfig(keyA, hostA, pixelA, segmentsA); - -const keyB = 'key-b'; -const hostB = 'host-b'; -const pixelB = 'pixel-b'; -const segmentsB = ['b']; -const userB = 'fs-user-b'; -const vuidB = 'vuid_b'; -const odpConfigB = new OdpConfig(keyB, hostB, pixelB, segmentsB); - -describe('OdpManager', () => { - let odpConfig: OdpConfig; - - let mockLogger: LogHandler; - let fakeLogger: LogHandler; - - let mockRequestHandler: RequestHandler; - let fakeRequestHandler: RequestHandler; - - let mockEventApiManager: BrowserOdpEventApiManager; - let fakeEventApiManager: BrowserOdpEventApiManager; - - let mockEventManager: BrowserOdpEventManager; - let fakeEventManager: BrowserOdpEventManager; - - let mockSegmentApiManager: OdpSegmentApiManager; - let fakeSegmentApiManager: OdpSegmentApiManager; - - let mockSegmentManager: OdpSegmentManager; - let fakeSegmentManager: OdpSegmentManager; - - let mockBrowserOdpManager: BrowserOdpManager; - let fakeBrowserOdpManager: BrowserOdpManager; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockRequestHandler = mock<RequestHandler>(); - - odpConfig = new OdpConfig(keyA, hostA, pixelA, segmentsA); - fakeLogger = instance(mockLogger); - fakeRequestHandler = instance(mockRequestHandler); - - mockEventApiManager = mock<BrowserOdpEventApiManager>(); - mockEventManager = mock<BrowserOdpEventManager>(); - mockSegmentApiManager = mock<OdpSegmentApiManager>(); - mockSegmentManager = mock<OdpSegmentManager>(); - mockBrowserOdpManager = mock<BrowserOdpManager>(); - - fakeEventApiManager = instance(mockEventApiManager); - fakeEventManager = instance(mockEventManager); - fakeSegmentApiManager = instance(mockSegmentApiManager); - fakeSegmentManager = instance(mockSegmentManager); - fakeBrowserOdpManager = instance(mockBrowserOdpManager); - }); - - beforeEach(() => { - resetCalls(mockLogger); - resetCalls(mockRequestHandler); - resetCalls(mockEventApiManager); - resetCalls(mockEventManager); - resetCalls(mockSegmentManager); - }); - - const browserOdpManagerInstance = () => - BrowserOdpManager.createInstance({ - odpOptions: { - eventManager: fakeEventManager, - segmentManager: fakeSegmentManager, - }, - }); - - it('should create VUID automatically on BrowserOdpManager initialization', async () => { - const browserOdpManager = browserOdpManagerInstance(); - const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); - expect(browserOdpManager.vuid).toBe(vuidManager.vuid); - }); - - describe('Populates BrowserOdpManager correctly with all odpOptions', () => { - beforeAll(() => { - - }); - - it('Custom odpOptions.segmentsCache overrides default LRUCache', () => { - const odpOptions: OdpOptions = { - segmentsCache: new BrowserLRUCache<string, string[]>({ - maxSize: 2, - timeout: 4000, - }), - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - const segmentManager = browserOdpManager['segmentManager'] as OdpSegmentManager; - - // @ts-ignore - expect(browserOdpManager.segmentManager._segmentsCache.maxSize).toBe(2); - - // @ts-ignore - expect(browserOdpManager.segmentManager._segmentsCache.timeout).toBe(4000); - }); - - it('Custom odpOptions.segmentsCacheSize overrides default LRUCache size', () => { - const odpOptions: OdpOptions = { - segmentsCacheSize: 2, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager._segmentsCache.maxSize).toBe(2); - }); - - it('Custom odpOptions.segmentsCacheTimeout overrides default LRUCache timeout', () => { - const odpOptions: OdpOptions = { - segmentsCacheTimeout: 4000, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager._segmentsCache.timeout).toBe(4000); - }); - - it('Custom odpOptions.segmentsApiTimeout overrides default Segment API Request Handler timeout', () => { - const odpOptions: OdpOptions = { - segmentsApiTimeout: 4000, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4000); - }); - - it('Browser default Segments API Request Handler timeout should be used when odpOptions does not include segmentsApiTimeout', () => { - const browserOdpManager = BrowserOdpManager.createInstance({}); - - // @ts-ignore - expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(10000); - }); - - it('Custom odpOptions.segmentsRequestHandler overrides default Segment API Request Handler', () => { - const odpOptions: OdpOptions = { - segmentsRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 4000 }), - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4000); - }); - - it('Custom odpOptions.segmentRequestHandler override takes precedence over odpOptions.eventApiTimeout', () => { - const odpOptions: OdpOptions = { - segmentsApiTimeout: 2, - segmentsRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(1); - }); - - it('Custom odpOptions.segmentManager overrides default Segment Manager', () => { - const customSegmentManager = new OdpSegmentManager( - new BrowserLRUCache<string, string[]>(), - fakeSegmentApiManager, - fakeLogger, - odpConfig, - ); - - const odpOptions: OdpOptions = { - segmentManager: customSegmentManager, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager).toBe(customSegmentManager); - }); - - it('Custom odpOptions.segmentManager override takes precedence over all other segments-related odpOptions', () => { - const customSegmentManager = new OdpSegmentManager( - new BrowserLRUCache<string, string[]>({ - maxSize: 1, - timeout: 1, - }), - new OdpSegmentApiManager(new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), fakeLogger), - fakeLogger, - odpConfig, - ); - - const odpOptions: OdpOptions = { - segmentsCacheSize: 2, - segmentsCacheTimeout: 2, - segmentsCache: new BrowserLRUCache<string, string[]>({ maxSize: 2, timeout: 2 }), - segmentsApiTimeout: 2, - segmentsRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 2 }), - segmentManager: customSegmentManager, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(1); - - // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(1); - - // @ts-ignore - expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(1); - - // @ts-ignore - expect(browserOdpManager.segmentManager).toBe(customSegmentManager); - }); - - it('Custom odpOptions.eventApiTimeout overrides default Event API Request Handler timeout', () => { - const odpOptions: OdpOptions = { - eventApiTimeout: 4000, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(4000); - }); - - it('Browser default Events API Request Handler timeout should be used when odpOptions does not include eventsApiTimeout', () => { - const odpOptions: OdpOptions = {}; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(10000); - }); - - it('Custom odpOptions.eventFlushInterval cannot override the default Event Manager flush interval', () => { - const odpOptions: OdpOptions = { - eventFlushInterval: 4000, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(0); // Note: Browser flush interval is always 0 due to use of Pixel API - }); - - it('Default ODP event flush interval is used when odpOptions does not include eventFlushInterval', () => { - const odpOptions: OdpOptions = {}; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(0); - }); - - it('ODP event batch size set to one when odpOptions.eventFlushInterval set to 0', () => { - const odpOptions: OdpOptions = { - eventFlushInterval: 0, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(0); - - // @ts-ignore - expect(browserOdpManager.eventManager.batchSize).toBe(1); - }); - - it('Custom odpOptions.eventBatchSize does not override default Event Manager batch size', () => { - const odpOptions: OdpOptions = { - eventBatchSize: 2, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.batchSize).toBe(1); // Note: Browser event batch size is always 1 due to use of Pixel API - }); - - it('Custom odpOptions.eventQueueSize overrides default Event Manager queue size', () => { - const odpOptions: OdpOptions = { - eventQueueSize: 2, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.queueSize).toBe(2); - }); - - it('Custom odpOptions.eventRequestHandler overrides default Event Manager request handler', () => { - const odpOptions: OdpOptions = { - eventRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 4000 }), - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(4000); - }); - - it('Custom odpOptions.eventRequestHandler override takes precedence over odpOptions.eventApiTimeout', () => { - const odpOptions: OdpOptions = { - eventApiTimeout: 2, - eventBatchSize: 2, - eventFlushInterval: 2, - eventQueueSize: 2, - eventRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(1); - }); - - it('Custom odpOptions.eventManager overrides default Event Manager', () => { - const fakeClientEngine = 'test-javascript-sdk'; - const fakeClientVersion = '1.2.3'; - - const customEventManager = new BrowserOdpEventManager({ - odpConfig, - apiManager: fakeEventApiManager, - logger: fakeLogger, - clientEngine: fakeClientEngine, - clientVersion: fakeClientVersion, - }); - - const odpOptions: OdpOptions = { - eventManager: customEventManager, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager).toBe(customEventManager); - - // @ts-ignore - expect(browserOdpManager.eventManager.clientEngine).toBe(fakeClientEngine); - - // @ts-ignore - expect(browserOdpManager.eventManager.clientVersion).toBe(fakeClientVersion); - }); - - it('Custom odpOptions.eventManager override takes precedence over all other event-related odpOptions', () => { - const fakeClientEngine = 'test-javascript-sdk'; - const fakeClientVersion = '1.2.3'; - - const customEventManager = new BrowserOdpEventManager({ - odpConfig, - apiManager: new BrowserOdpEventApiManager(new BrowserRequestHandler({ logger: fakeLogger, timeout: 1 }), fakeLogger), - logger: fakeLogger, - clientEngine: fakeClientEngine, - clientVersion: fakeClientVersion, - queueSize: 1, - batchSize: 1, - flushInterval: 1, - }); - - const odpOptions: OdpOptions = { - eventApiTimeout: 2, - eventBatchSize: 2, - eventFlushInterval: 2, - eventQueueSize: 2, - eventRequestHandler: new BrowserRequestHandler({ logger: fakeLogger, timeout: 3 }), - eventManager: customEventManager, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.eventManager).toBe(customEventManager); - - // @ts-ignore - expect(browserOdpManager.eventManager.clientEngine).toBe(fakeClientEngine); - - // @ts-ignore - expect(browserOdpManager.eventManager.clientVersion).toBe(fakeClientVersion); - - // @ts-ignore - expect(browserOdpManager.eventManager.batchSize).toBe(1); - - // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(0); // Note: Browser event flush interval will always be 0 due to use of Pixel API - - // @ts-ignore - expect(browserOdpManager.eventManager.queueSize).toBe(1); - - // @ts-ignore - expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(1); - }); - - it('Custom odpOptions micro values (non-request/manager) override all expected fields for both segments and event managers', () => { - const odpOptions: OdpOptions = { - segmentsCacheSize: 4, - segmentsCacheTimeout: 4, - segmentsCache: new BrowserLRUCache<string, string[]>({ maxSize: 4, timeout: 4 }), - segmentsApiTimeout: 4, - eventApiTimeout: 4, - eventBatchSize: 4, - eventFlushInterval: 4, - eventQueueSize: 4, - }; - - const browserOdpManager = BrowserOdpManager.createInstance({ - odpOptions, - }); - - // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(4); - - // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4); - - // @ts-ignore - expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(4); - - // @ts-ignore - expect(browserOdpManager.eventManager.batchSize).toBe(1); // Note: Browser batch size will always be 1 due to use of Pixel API - - // @ts-ignore - expect(browserOdpManager.eventManager.flushInterval).toBe(0); // Note: Browser event flush interval will always be 0 due to use of Pixel API - - // @ts-ignore - expect(browserOdpManager.eventManager.queueSize).toBe(4); - - // @ts-ignore - expect(browserOdpManager.eventManager.apiManager.requestHandler.timeout).toBe(4); - }); - }); -}); diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts deleted file mode 100644 index 96f69b353..000000000 --- a/tests/odpManager.spec.ts +++ /dev/null @@ -1,698 +0,0 @@ -/** - * Copyright 2023-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, beforeAll, it, vi, expect } from 'vitest'; - -import { anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; - -import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; - -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; - -import { OdpManager, Status } from '../lib/odp/odp_manager'; -import { OdpConfig, OdpIntegratedConfig, OdpIntegrationConfig, OdpNotIntegratedConfig } from '../lib/odp/odp_config'; -import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/odp/event_manager/event_api_manager.node'; -import { NodeOdpEventManager as OdpEventManager } from '../lib/odp/event_manager/event_manager.node'; -import { IOdpSegmentManager, OdpSegmentManager } from './../lib/odp/segment_manager/odp_segment_manager'; -import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; -import { IOdpEventManager } from '../lib/shared_types'; -import { wait } from './testUtils'; -import { resolvablePromise } from '../lib/utils/promise/resolvablePromise'; - -const keyA = 'key-a'; -const hostA = 'host-a'; -const pixelA = 'pixel-a'; -const segmentsA = ['a']; -const userA = 'fs-user-a'; - -const keyB = 'key-b'; -const hostB = 'host-b'; -const pixelB = 'pixel-b'; -const segmentsB = ['b']; -const userB = 'fs-user-b'; - -const testOdpManager = ({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled, - vuid, - vuidInitializer, -}: { - odpIntegrationConfig?: OdpIntegrationConfig; - segmentManager: IOdpSegmentManager; - eventManager: IOdpEventManager; - logger: LogHandler; - vuidEnabled?: boolean; - vuid?: string; - vuidInitializer?: () => Promise<void>; -}): OdpManager => { - class TestOdpManager extends OdpManager{ - constructor() { - super({ odpIntegrationConfig, segmentManager, eventManager, logger }); - } - isVuidEnabled(): boolean { - return vuidEnabled ?? false; - } - getVuid(): string { - return vuid ?? 'vuid_123'; - } - protected initializeVuid(): Promise<void> { - return vuidInitializer?.() ?? Promise.resolve(); - } - } - return new TestOdpManager(); -} - -describe('OdpManager', () => { - let mockLogger: LogHandler; - let mockRequestHandler: RequestHandler; - - let odpConfig: OdpConfig; - let logger: LogHandler; - let defaultRequestHandler: RequestHandler; - - let mockEventApiManager: OdpEventApiManager; - let mockEventManager: OdpEventManager; - let mockSegmentApiManager: OdpSegmentApiManager; - let mockSegmentManager: OdpSegmentManager; - - let eventApiManager: OdpEventApiManager; - let eventManager: OdpEventManager; - let segmentApiManager: OdpSegmentApiManager; - let segmentManager: OdpSegmentManager; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockRequestHandler = mock<RequestHandler>(); - - logger = instance(mockLogger); - defaultRequestHandler = instance(mockRequestHandler); - - mockEventApiManager = mock<OdpEventApiManager>(); - mockEventManager = mock<OdpEventManager>(); - mockSegmentApiManager = mock<OdpSegmentApiManager>(); - mockSegmentManager = mock<OdpSegmentManager>(); - - eventApiManager = instance(mockEventApiManager); - eventManager = instance(mockEventManager); - segmentApiManager = instance(mockSegmentApiManager); - segmentManager = instance(mockSegmentManager); - }); - - beforeEach(() => { - resetCalls(mockLogger); - resetCalls(mockRequestHandler); - resetCalls(mockEventApiManager); - resetCalls(mockEventManager); - resetCalls(mockSegmentManager); - }); - - - it('should be in stopped status and not ready if constructed without odpIntegrationConfig', () => { - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - }); - - expect(odpManager.isReady()).toBe(false); - expect(odpManager.getStatus()).toEqual(Status.Stopped); - }); - - it('should call initialzeVuid on construction if vuid is enabled', () => { - const vuidInitializer = vi.fn(); - - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: true, - vuidInitializer: vuidInitializer, - }); - - expect(vuidInitializer).toHaveBeenCalledTimes(1); - }); - - it('should become ready only after odpIntegrationConfig is provided if vuid is not enabled', async () => { - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: false, - }); - - // should not be ready untill odpIntegrationConfig is provided - await wait(500); - expect(odpManager.isReady()).toBe(false); - - const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; - odpManager.updateSettings(odpIntegrationConfig); - - await odpManager.onReady(); - expect(odpManager.isReady()).toBe(true); - }); - - it('should become ready if odpIntegrationConfig is provided in constructor and then initialzeVuid', async () => { - const vuidPromise = resolvablePromise<void>(); - const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; - - const vuidInitializer = () => { - return vuidPromise.promise; - } - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - vuidInitializer, - }); - - await wait(500); - expect(odpManager.isReady()).toBe(false); - - vuidPromise.resolve(); - - await odpManager.onReady(); - expect(odpManager.isReady()).toBe(true); - }); - - it('should become ready after odpIntegrationConfig is provided using updateSettings() and then initialzeVuid finishes', async () => { - const vuidPromise = resolvablePromise<void>(); - - const vuidInitializer = () => { - return vuidPromise.promise; - } - - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: true, - vuidInitializer, - }); - - - expect(odpManager.isReady()).toBe(false); - - const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; - odpManager.updateSettings(odpIntegrationConfig); - - await wait(500); - expect(odpManager.isReady()).toBe(false); - - vuidPromise.resolve(); - - await odpManager.onReady(); - expect(odpManager.isReady()).toBe(true); - }); - - it('should become ready after initialzeVuid finishes and then odpIntegrationConfig is provided using updateSettings()', async () => { - const vuidPromise = resolvablePromise<void>(); - - const vuidInitializer = () => { - return vuidPromise.promise; - } - - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: true, - vuidInitializer, - }); - - expect(odpManager.isReady()).toBe(false); - vuidPromise.resolve(); - - await wait(500); - expect(odpManager.isReady()).toBe(false); - - const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; - odpManager.updateSettings(odpIntegrationConfig); - - await odpManager.onReady(); - expect(odpManager.isReady()).toBe(true); - }); - - it('should become ready and stay in stopped state and not start eventManager if OdpNotIntegrated config is provided', async () => { - const vuidPromise = resolvablePromise<void>(); - - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; - odpManager.updateSettings(odpIntegrationConfig); - - await odpManager.onReady(); - expect(odpManager.isReady()).toBe(true); - expect(odpManager.getStatus()).toEqual(Status.Stopped); - verify(mockEventManager.start()).never(); - }); - - it('should pass the integrated odp config given in constructor to eventManger and segmentManager', async () => { - when(mockEventManager.updateSettings(anything())).thenReturn(undefined); - when(mockSegmentManager.updateSettings(anything())).thenReturn(undefined); - - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - verify(mockEventManager.updateSettings(anything())).once(); - const [eventOdpConfig] = capture(mockEventManager.updateSettings).first(); - expect(eventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); - - verify(mockSegmentManager.updateSettings(anything())).once(); - const [segmentOdpConfig] = capture(mockEventManager.updateSettings).first(); - expect(segmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); - }); - - it('should pass the integrated odp config given in updateSettings() to eventManger and segmentManager', async () => { - when(mockEventManager.updateSettings(anything())).thenReturn(undefined); - when(mockSegmentManager.updateSettings(anything())).thenReturn(undefined); - - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - odpManager.updateSettings(odpIntegrationConfig); - - verify(mockEventManager.updateSettings(anything())).once(); - const [eventOdpConfig] = capture(mockEventManager.updateSettings).first(); - expect(eventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); - - verify(mockSegmentManager.updateSettings(anything())).once(); - const [segmentOdpConfig] = capture(mockEventManager.updateSettings).first(); - expect(segmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); - }); - - it('should start if odp is integrated and start odpEventManger', async () => { - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - odpManager.updateSettings(odpIntegrationConfig); - await odpManager.onReady(); - expect(odpManager.isReady()).toBe(true); - expect(odpManager.getStatus()).toEqual(Status.Running); - }); - - it('should just update config when updateSettings is called in running state', async () => { - const odpManager = testOdpManager({ - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - odpManager.updateSettings(odpIntegrationConfig); - - await odpManager.onReady(); - expect(odpManager.isReady()).toBe(true); - expect(odpManager.getStatus()).toEqual(Status.Running); - - const newOdpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyB, hostB, pixelB, segmentsB) - }; - - odpManager.updateSettings(newOdpIntegrationConfig); - - verify(mockEventManager.start()).once(); - verify(mockEventManager.stop()).never(); - verify(mockEventManager.updateSettings(anything())).twice(); - const [firstEventOdpConfig] = capture(mockEventManager.updateSettings).first(); - expect(firstEventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); - const [secondEventOdpConfig] = capture(mockEventManager.updateSettings).second(); - expect(secondEventOdpConfig.equals(newOdpIntegrationConfig.odpConfig)).toBe(true); - - verify(mockSegmentManager.updateSettings(anything())).twice(); - const [firstSegmentOdpConfig] = capture(mockEventManager.updateSettings).first(); - expect(firstSegmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); - const [secondSegmentOdpConfig] = capture(mockEventManager.updateSettings).second(); - expect(secondSegmentOdpConfig.equals(newOdpIntegrationConfig.odpConfig)).toBe(true); - }); - - it('should stop and stop eventManager if OdpNotIntegrated config is updated in running state', async () => { - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - expect(odpManager.isReady()).toBe(true); - expect(odpManager.getStatus()).toEqual(Status.Running); - - const newOdpIntegrationConfig: OdpNotIntegratedConfig = { - integrated: false, - }; - - odpManager.updateSettings(newOdpIntegrationConfig); - - expect(odpManager.getStatus()).toEqual(Status.Stopped); - verify(mockEventManager.stop()).once(); - }); - - it('should register vuid after becoming ready if odp is integrated', async () => { - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - verify(mockEventManager.registerVuid(anything())).once(); - }); - - it('should call eventManager.identifyUser with correct parameters when identifyUser is called', async () => { - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - const userId = 'user123'; - const vuid = 'vuid_123'; - - odpManager.identifyUser(userId, vuid); - const [userIdArg, vuidArg] = capture(mockEventManager.identifyUser).byCallIndex(0); - expect(userIdArg).toEqual(userId); - expect(vuidArg).toEqual(vuid); - - odpManager.identifyUser(userId); - const [userIdArg2, vuidArg2] = capture(mockEventManager.identifyUser).byCallIndex(1); - expect(userIdArg2).toEqual(userId); - expect(vuidArg2).toEqual(undefined); - - odpManager.identifyUser(vuid); - const [userIdArg3, vuidArg3] = capture(mockEventManager.identifyUser).byCallIndex(2); - expect(userIdArg3).toEqual(undefined); - expect(vuidArg3).toEqual(vuid); - }); - - it('should send event with correct parameters', async () => { - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - const identifiers = new Map([['email', 'a@b.com']]); - const data = new Map([['key1', 'value1'], ['key2', 'value2']]); - - odpManager.sendEvent({ - action: 'action', - type: 'type', - identifiers, - data, - }); - - const [event] = capture(mockEventManager.sendEvent).byCallIndex(0); - expect(event.action).toEqual('action'); - expect(event.type).toEqual('type'); - expect(event.identifiers).toEqual(identifiers); - expect(event.data).toEqual(data); - - // should use `fullstack` as type if empty string is provided - odpManager.sendEvent({ - type: '', - action: 'action', - identifiers, - data, - }); - - const [event2] = capture(mockEventManager.sendEvent).byCallIndex(1); - expect(event2.action).toEqual('action'); - expect(event2.type).toEqual('fullstack'); - expect(event2.identifiers).toEqual(identifiers); - }); - - - it('should throw an error if event action is empty string and not call eventManager', async () => { - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - const identifiers = new Map([['email', 'a@b.com']]); - const data = new Map([['key1', 'value1'], ['key2', 'value2']]); - - const sendEvent = () => odpManager.sendEvent({ - action: '', - type: 'type', - identifiers, - data, - }); - - expect(sendEvent).toThrow('ODP action is not valid'); - verify(mockEventManager.sendEvent(anything())).never(); - }); - - it('should throw an error if event data is invalid', async () => { - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - const identifiers = new Map([['email', 'a@b.com']]); - const data = new Map([['key1', {}]]); - - const sendEvent = () => odpManager.sendEvent({ - action: 'action', - type: 'type', - identifiers, - data, - }); - - expect(sendEvent).toThrow(ERROR_MESSAGES.ODP_INVALID_DATA); - verify(mockEventManager.sendEvent(anything())).never(); - }); - - it('should fetch qualified segments correctly for both fs_user_id and vuid', async () => { - const userId = 'user123'; - const vuid = 'vuid_123'; - - when(mockSegmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, anything())) - .thenResolve(['fs1', 'fs2']); - - when(mockSegmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, vuid, anything())) - .thenResolve(['vuid1', 'vuid2']); - - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager: instance(mockSegmentManager), - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - const fsSegments = await odpManager.fetchQualifiedSegments(userId); - expect(fsSegments).toEqual(['fs1', 'fs2']); - - const vuidSegments = await odpManager.fetchQualifiedSegments(vuid); - expect(vuidSegments).toEqual(['vuid1', 'vuid2']); - }); - - - it('should stop itself and eventManager if stop is called', async () => { - const odpIntegrationConfig: OdpIntegratedConfig = { - integrated: true, - odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) - }; - - const odpManager = testOdpManager({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - odpManager.stop(); - - expect(odpManager.getStatus()).toEqual(Status.Stopped); - verify(mockEventManager.stop()).once(); - }); - - - - it('should drop relevant calls and log error when odpIntegrationConfig is not available', async () => { - const odpManager = testOdpManager({ - odpIntegrationConfig: undefined, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - const segments = await odpManager.fetchQualifiedSegments('vuid_user1', []); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).once(); - expect(segments).toBeNull(); - - odpManager.identifyUser('vuid_user1'); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).twice(); - verify(mockEventManager.identifyUser(anything(), anything())).never(); - - const identifiers = new Map([['email', 'a@b.com']]); - const data = new Map([['key1', {}]]); - - odpManager.sendEvent({ - action: 'action', - type: 'type', - identifiers, - data, - }); - - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).thrice(); - verify(mockEventManager.sendEvent(anything())).never(); - - }); - - it('should drop relevant calls and log error when odp is not integrated', async () => { - const odpManager = testOdpManager({ - odpIntegrationConfig: { integrated: false }, - segmentManager, - eventManager, - logger, - vuidEnabled: true, - }); - - await odpManager.onReady(); - - const segments = await odpManager.fetchQualifiedSegments('vuid_user1', []); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).once(); - expect(segments).toBeNull(); - - odpManager.identifyUser('vuid_user1'); - verify(mockLogger.log(LogLevel.INFO, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).once(); - verify(mockEventManager.identifyUser(anything(), anything())).never(); - - const identifiers = new Map([['email', 'a@b.com']]); - const data = new Map([['key1', {}]]); - - odpManager.sendEvent({ - action: 'action', - type: 'type', - identifiers, - data, - }); - - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).twice(); - verify(mockEventManager.sendEvent(anything())).never(); - }); -}); - diff --git a/tests/odpSegmentApiManager.spec.ts b/tests/odpSegmentApiManager.spec.ts deleted file mode 100644 index ee8ebc482..000000000 --- a/tests/odpSegmentApiManager.spec.ts +++ /dev/null @@ -1,300 +0,0 @@ -/** - * Copyright 2022-2024 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; - -import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; -import { ODP_USER_KEY } from '../lib/utils/enums'; - -const API_key = 'not-real-api-key'; -const GRAPHQL_ENDPOINT = 'https://some.example.com/graphql/endpoint'; -const USER_KEY = ODP_USER_KEY.FS_USER_ID; -const USER_VALUE = 'tester-101'; -const SEGMENTS_TO_CHECK = ['has_email', 'has_email_opted_in', 'push_on_sale']; - -describe('OdpSegmentApiManager', () => { - let mockLogger: LogHandler; - let mockRequestHandler: RequestHandler; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockRequestHandler = mock<RequestHandler>(); - }); - - beforeEach(() => { - resetCalls(mockLogger); - resetCalls(mockRequestHandler); - }); - - const managerInstance = () => new OdpSegmentApiManager(instance(mockRequestHandler), instance(mockLogger)); - - const abortableRequest = (statusCode: number, body: string) => { - return { - abort: () => {}, - responsePromise: Promise.resolve({ - statusCode, - body, - headers: {}, - }), - }; - }; - - it('should parse a successful response', () => { - const validJsonResponse = `{ - "data": { - "customer": { - "audiences": { - "edges": [ - { - "node": { - "name": "has_email", - "state": "qualified" - } - }, - { - "node": { - "name": "has_email_opted_in", - "state": "not-qualified" - } - } - ] - } - } - } - }`; - const manager = managerInstance(); - - const response = manager['parseSegmentsResponseJson'](validJsonResponse); - - expect(response).not.toBeUndefined(); - expect(response?.errors).toHaveLength(0); - expect(response?.data?.customer?.audiences?.edges).not.toBeNull(); - expect(response?.data.customer.audiences.edges).toHaveLength(2); - let node = response?.data.customer.audiences.edges[0].node; - expect(node?.name).toEqual('has_email'); - expect(node?.state).toEqual('qualified'); - node = response?.data.customer.audiences.edges[1].node; - expect(node?.name).toEqual('has_email_opted_in'); - expect(node?.state).not.toEqual('qualified'); - }); - - it('should parse an error response', () => { - const errorJsonResponse = `{ - "errors": [ - { - "message": "Exception while fetching data (/customer) : Exception: could not resolve _fs_user_id = mock_user_id", - "locations": [ - { - "line": 2, - "column": 3 - } - ], - "path": [ - "customer" - ], - "extensions": { - "classification": "InvalidIdentifierException" - } - } - ], - "data": { - "customer": null - } -}`; - const manager = managerInstance(); - - const response = manager['parseSegmentsResponseJson'](errorJsonResponse); - - expect(response).not.toBeUndefined(); - expect(response?.data.customer).toBeNull(); - expect(response?.errors).not.toBeNull(); - expect(response?.errors[0].extensions.classification).toEqual('InvalidIdentifierException'); - }); - - it('should construct a valid GraphQL query string', () => { - const manager = managerInstance(); - - const response = manager['toGraphQLJson'](USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(response).toBe( - `{"query" : "query {customer(${USER_KEY} : \\"${USER_VALUE}\\") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"]) {edges {node {name state}}}}}"}` - ); - }); - - it('should fetch valid qualified segments', async () => { - const responseJsonWithQualifiedSegments = - '{"data":{"customer":{"audiences":' + - '{"edges":[{"node":{"name":"has_email",' + - '"state":"qualified"}},{"node":{"name":' + - '"has_email_opted_in","state":"qualified"}}]}}}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, responseJsonWithQualifiedSegments) - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments?.length).toEqual(2); - expect(segments).toContain('has_email'); - expect(segments).toContain('has_email_opted_in'); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should handle a request to query no segments', async () => { - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, ODP_USER_KEY.FS_USER_ID, USER_VALUE, []); - - expect(segments?.length).toEqual(0); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should handle empty qualified segments', async () => { - const responseJsonWithNoQualifiedSegments = '{"data":{"customer":{"audiences":' + '{"edges":[ ]}}}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, responseJsonWithNoQualifiedSegments) - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments?.length).toEqual(0); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should handle error with invalid identifier', async () => { - const INVALID_USER_ID = 'invalid-user'; - const errorJsonResponse = - '{"errors":[{"message":' + - '"Exception while fetching data (/customer) : ' + - `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + - '"locations":[{"line":1,"column":8}],"path":["customer"],' + - '"extensions":{"code": "INVALID_IDENTIFIER_EXCEPTION","classification":"DataFetchingException"}}],' + - '"data":{"customer":null}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, errorJsonResponse) - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments( - API_key, - GRAPHQL_ENDPOINT, - USER_KEY, - INVALID_USER_ID, - SEGMENTS_TO_CHECK - ); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (invalid identifier)')).once(); - }); - - it('should handle other fetch error responses', async () => { - const INVALID_USER_ID = 'invalid-user'; - const errorJsonResponse = - '{"errors":[{"message":' + - '"Exception while fetching data (/customer) : ' + - `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + - '"locations":[{"line":1,"column":8}],"path":["customer"],' + - '"extensions":{"classification":"DataFetchingException"}}],' + - '"data":{"customer":null}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, errorJsonResponse) - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments( - API_key, - GRAPHQL_ENDPOINT, - USER_KEY, - INVALID_USER_ID, - SEGMENTS_TO_CHECK - ); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (DataFetchingException)')).once(); - }); - - it('should handle unrecognized JSON responses', async () => { - const unrecognizedJson = '{"unExpectedObject":{ "withSome": "value", "thatIsNotParseable": "true" }}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, unrecognizedJson) - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (decode error)')).once(); - }); - - it('should handle other exception types', async () => { - const errorJsonResponse = - '{"errors":[{"message":"Validation error of type ' + - 'UnknownArgument: Unknown field argument not_real_userKey @ ' + - '\'customer\'","locations":[{"line":1,"column":17}],' + - '"extensions":{"classification":"ValidationError"}}]}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, errorJsonResponse) - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(anything(), anyString())).once(); - }); - - it('should handle bad responses', async () => { - const badResponse = '{"data":{ }}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(200, badResponse) - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (decode error)')).once(); - }); - - it('should handle non 200 HTTP status code response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn( - abortableRequest(400, '') - ); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (network error)')).once(); - }); - - it('should handle a timeout', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ - abort: () => {}, - responsePromise: Promise.reject(new Error('Request timed out')), - }); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (network error)')).once(); - }); -}); diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts deleted file mode 100644 index f10dbc353..000000000 --- a/tests/odpSegmentManager.spec.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright 2022-2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, it, expect } from 'vitest'; - -import { mock, resetCalls, instance } from 'ts-mockito'; - -import { LogHandler } from '../lib/modules/logging'; -import { ODP_USER_KEY } from '../lib/utils/enums'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; - -import { OdpSegmentManager } from '../lib/odp/segment_manager/odp_segment_manager'; -import { OdpConfig } from '../lib/odp/odp_config'; -import { LRUCache } from '../lib/utils/lru_cache'; -import { OptimizelySegmentOption } from './../lib/odp/segment_manager/optimizely_segment_option'; -import { OdpSegmentApiManager } from '../lib/odp/segment_manager/odp_segment_api_manager'; - -describe('OdpSegmentManager', () => { - class MockOdpSegmentApiManager extends OdpSegmentApiManager { - async fetchSegments( - apiKey: string, - apiHost: string, - userKey: ODP_USER_KEY, - userValue: string, - segmentsToCheck: string[] - ): Promise<string[] | null> { - if (apiKey == 'invalid-key') return null; - return segmentsToCheck; - } - } - - const mockLogHandler = mock<LogHandler>(); - const mockRequestHandler = mock<RequestHandler>(); - - const apiManager = new MockOdpSegmentApiManager(instance(mockRequestHandler), instance(mockLogHandler)); - - let options: Array<OptimizelySegmentOption> = []; - - const userKey: ODP_USER_KEY = ODP_USER_KEY.VUID; - const userValue = 'test-user'; - - const validTestOdpConfig = new OdpConfig('valid-key', 'host', 'pixel-url', ['new-customer']); - const invalidTestOdpConfig = new OdpConfig('invalid-key', 'host', 'pixel-url', ['new-customer']); - - const getSegmentsCache = () => { - return new LRUCache<string, string[]>({ - maxSize: 1000, - timeout: 1000, - }); - } - - beforeEach(() => { - resetCalls(mockLogHandler); - resetCalls(mockRequestHandler); - - const API_KEY = 'test-api-key'; - const API_HOST = 'https://odp.example.com/'; - const PIXEL_URL = 'https://odp.pixel.com/'; - }); - - it('should fetch segments successfully on cache miss.', async () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - setCache(manager, userKey, '123', ['a']); - - const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); - expect(segments).toEqual(['new-customer']); - }); - - it('should fetch segments successfully on cache hit.', async () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - setCache(manager, userKey, userValue, ['a']); - - const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); - expect(segments).toEqual(['a']); - }); - - it('should return null when fetching segments returns an error.', async () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, invalidTestOdpConfig); - - const segments = await manager.fetchQualifiedSegments(userKey, userValue, []); - expect(segments).toBeNull; - }); - - it('should ignore the cache if the option enum is included in the options array.', async () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - setCache(manager, userKey, userValue, ['a']); - options = [OptimizelySegmentOption.IGNORE_CACHE]; - - const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); - expect(segments).toEqual(['new-customer']); - expect(cacheCount(manager)).toBe(1); - }); - - it('should ignore the cache if the option string is included in the options array.', async () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - setCache(manager,userKey, userValue, ['a']); - // @ts-ignore - options = ['IGNORE_CACHE']; - - const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); - expect(segments).toEqual(['new-customer']); - expect(cacheCount(manager)).toBe(1); - }); - - it('should reset the cache if the option enum is included in the options array.', async () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - setCache(manager, userKey, userValue, ['a']); - setCache(manager, userKey, '123', ['a']); - setCache(manager, userKey, '456', ['a']); - options = [OptimizelySegmentOption.RESET_CACHE]; - - const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); - expect(segments).toEqual(['new-customer']); - expect(peekCache(manager, userKey, userValue)).toEqual(segments); - expect(cacheCount(manager)).toBe(1); - }); - - it('should reset the cache on settings update.', async () => { - const oldConfig = new OdpConfig('old-key', 'old-host', 'pixel-url', ['new-customer']); - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - - setCache(manager, userKey, userValue, ['a']); - expect(cacheCount(manager)).toBe(1); - - const newConfig = new OdpConfig('new-key', 'new-host', 'pixel-url', ['new-customer']); - manager.updateSettings(newConfig); - - expect(cacheCount(manager)).toBe(0); - }); - - it('should reset the cache if the option string is included in the options array.', async () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - setCache(manager, userKey, userValue, ['a']); - setCache(manager, userKey, '123', ['a']); - setCache(manager, userKey, '456', ['a']); - // @ts-ignore - options = ['RESET_CACHE']; - - const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); - expect(segments).toEqual(['new-customer']); - expect(peekCache(manager, userKey, userValue)).toEqual(segments); - expect(cacheCount(manager)).toBe(1); - }); - - it('should make a valid cache key.', () => { - const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); - expect('vuid-$-test-user').toBe(manager.makeCacheKey(userKey, userValue)); - }); - - // Utility Functions - - function setCache(manager: OdpSegmentManager, userKey: string, userValue: string, value: string[]) { - const cacheKey = manager.makeCacheKey(userKey, userValue); - manager.segmentsCache.save({ - key: cacheKey, - value, - }); - } - - function peekCache(manager: OdpSegmentManager, userKey: string, userValue: string): string[] | null { - const cacheKey = manager.makeCacheKey(userKey, userValue); - return (manager.segmentsCache as LRUCache<string, string[]>).peek(cacheKey); - } - - const cacheCount = (manager: OdpSegmentManager) => (manager.segmentsCache as LRUCache<string, string[]>).map.size; -}); diff --git a/tests/vuidManager.spec.ts b/tests/vuidManager.spec.ts deleted file mode 100644 index 2f412fe02..000000000 --- a/tests/vuidManager.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, beforeAll, it, expect } from 'vitest'; - -import { VuidManager } from '../lib/plugins/vuid_manager'; -import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; -import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; - -describe('VuidManager', () => { - let mockCache: PersistentKeyValueCache<string>; - - beforeAll(() => { - mockCache = mock<PersistentKeyValueCache>(); - when(mockCache.contains(anyString())).thenResolve(true); - when(mockCache.get(anyString())).thenResolve(''); - when(mockCache.remove(anyString())).thenResolve(true); - when(mockCache.set(anyString(), anything())).thenResolve(); - VuidManager.instance(instance(mockCache)); - }); - - beforeEach(() => { - resetCalls(mockCache); - VuidManager['_reset'](); - }); - - it('should make a VUID', async () => { - const manager = await VuidManager.instance(instance(mockCache)); - - const vuid = manager['makeVuid'](); - - expect(vuid.startsWith('vuid_')).toBe(true); - expect(vuid.length).toEqual(32); - expect(vuid).not.toContain('-'); - }); - - it('should test if a VUID is valid', async () => { - const manager = await VuidManager.instance(instance(mockCache)); - - expect(VuidManager.isVuid('vuid_123')).toBe(true); - expect(VuidManager.isVuid('vuid-123')).toBe(false); - expect(VuidManager.isVuid('123')).toBe(false); - }); - - it('should auto-save and auto-load', async () => { - const cache = instance(mockCache); - - await cache.remove('optimizely-odp'); - - const manager1 = await VuidManager.instance(cache); - const vuid1 = manager1.vuid; - - const manager2 = await VuidManager.instance(cache); - const vuid2 = manager2.vuid; - - expect(vuid1).toStrictEqual(vuid2); - expect(VuidManager.isVuid(vuid1)).toBe(true); - expect(VuidManager.isVuid(vuid2)).toBe(true); - - await cache.remove('optimizely-odp'); - - // should end up being a new instance since we just removed it above - await manager2['load'](cache); - const vuid3 = manager2.vuid; - - expect(vuid3).not.toStrictEqual(vuid1); - expect(VuidManager.isVuid(vuid3)).toBe(true); - }); - - it('should handle no valid optimizely-vuid in the cache', async () => { - when(mockCache.get(anyString())).thenResolve(undefined); - - const manager = await VuidManager.instance(instance(mockCache)); // load() called initially - - verify(mockCache.get(anyString())).once(); - verify(mockCache.set(anyString(), anything())).once(); - expect(VuidManager.isVuid(manager.vuid)).toBe(true); - }); - - it('should create a new vuid if old VUID from cache is not valid', async () => { - when(mockCache.get(anyString())).thenResolve('vuid-not-valid'); - - const manager = await VuidManager.instance(instance(mockCache)); - - verify(mockCache.get(anyString())).once(); - verify(mockCache.set(anyString(), anything())).once(); - expect(VuidManager.isVuid(manager.vuid)).toBe(true); - }); -}); From 787fbc022dcb1553fd4e47e37794665b26b012e6 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 24 Dec 2024 20:56:25 +0600 Subject: [PATCH 106/200] [FSSDK-10950] cleanup of test files (#978) --- .../batch_event_processor.spec.ts | 2 +- .../default_dispatcher.browser.spec.ts | 4 +- .../default_dispatcher.browser.ts | 2 +- .../default_dispatcher.node.spec.ts | 4 +- .../default_dispatcher.node.ts | 2 +- lib/index.lite.ts | 4 +- lib/index.node.ts | 1 - lib/index.react_native.spec.ts | 177 +++++++++ {tests => lib/modules/logging}/logger.spec.ts | 9 +- .../event_manager/odp_event_manager.spec.ts | 2 +- lib/odp/odp_manager_factory.browser.spec.ts | 4 +- lib/odp/odp_manager_factory.browser.ts | 2 +- lib/odp/odp_manager_factory.node.spec.ts | 4 +- lib/odp/odp_manager_factory.node.ts | 2 +- .../odp_manager_factory.react_native.spec.ts | 4 +- lib/odp/odp_manager_factory.react_native.ts | 2 +- .../persistentKeyValueCache.ts | 62 ---- .../reactNativeAsyncStorageCache.ts | 42 --- .../config_manager_factory.browser.spec.ts | 4 +- .../config_manager_factory.browser.ts | 2 +- .../config_manager_factory.node.spec.ts | 4 +- .../config_manager_factory.node.ts | 2 +- ...onfig_manager_factory.react_native.spec.ts | 4 +- .../config_manager_factory.react_native.ts | 2 +- .../project_config_manager.spec.ts | 2 +- lib/shared_types.ts | 17 +- lib/tests/testUtils.ts | 7 + .../executor/backoff_retry_runner.spec.ts | 2 +- .../utils/fns/index.spec.ts | 2 +- .../request_handler.browser.spec.ts | 4 +- ..._handler.ts => request_handler.browser.ts} | 0 .../request_handler.node.spec.ts | 4 +- ...est_handler.ts => request_handler.node.ts} | 0 lib/utils/repeater/repeater.spec.ts | 2 +- tests/index.react_native.spec.ts | 346 ------------------ tests/reactNativeAsyncStorageCache.spec.ts | 74 ---- tests/testUtils.ts | 61 --- 37 files changed, 226 insertions(+), 642 deletions(-) create mode 100644 lib/index.react_native.spec.ts rename {tests => lib/modules/logging}/logger.spec.ts (98%) delete mode 100644 lib/plugins/key_value_cache/persistentKeyValueCache.ts delete mode 100644 lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts rename tests/utils.spec.ts => lib/utils/fns/index.spec.ts (98%) rename tests/browserRequestHandler.spec.ts => lib/utils/http_request_handler/request_handler.browser.spec.ts (96%) rename lib/utils/http_request_handler/{browser_request_handler.ts => request_handler.browser.ts} (100%) rename tests/nodeRequestHandler.spec.ts => lib/utils/http_request_handler/request_handler.node.spec.ts (98%) rename lib/utils/http_request_handler/{node_request_handler.ts => request_handler.node.ts} (100%) delete mode 100644 tests/index.react_native.spec.ts delete mode 100644 tests/reactNativeAsyncStorageCache.spec.ts delete mode 100644 tests/testUtils.ts diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index da02908ed..30d8d1bac 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -21,7 +21,7 @@ import { createImpressionEvent } from '../tests/mock/create_event'; import { ProcessableEvent } from './event_processor'; import { buildLogEvent } from './event_builder/log_event'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; -import { advanceTimersByTime } from '../../tests/testUtils'; +import { advanceTimersByTime } from '../tests/testUtils'; import { getMockLogger } from '../tests/mock/mock_logger'; import { getMockRepeater } from '../tests/mock/mock_repeater'; import * as retry from '../utils/executor/backoff_retry_runner'; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts index bf83ca13b..82314cbc7 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts @@ -21,13 +21,13 @@ vi.mock('./default_dispatcher', () => { return { DefaultEventDispatcher }; }); -vi.mock('../../utils/http_request_handler/browser_request_handler', () => { +vi.mock('../../utils/http_request_handler/request_handler.browser', () => { const BrowserRequestHandler = vi.fn(); return { BrowserRequestHandler }; }); import { DefaultEventDispatcher } from './default_dispatcher'; -import { BrowserRequestHandler } from '../../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../../utils/http_request_handler/request_handler.browser'; import eventDispatcher from './default_dispatcher.browser'; describe('eventDispatcher', () => { diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts index 893039d92..d38d266aa 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BrowserRequestHandler } from "../../utils/http_request_handler/browser_request_handler"; +import { BrowserRequestHandler } from "../../utils/http_request_handler/request_handler.browser"; import { EventDispatcher } from './event_dispatcher'; import { DefaultEventDispatcher } from './default_dispatcher'; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts index abd319b09..084fcce67 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts @@ -20,13 +20,13 @@ vi.mock('./default_dispatcher', () => { return { DefaultEventDispatcher }; }); -vi.mock('../../utils/http_request_handler/node_request_handler', () => { +vi.mock('../../utils/http_request_handler/request_handler.node', () => { const NodeRequestHandler = vi.fn(); return { NodeRequestHandler }; }); import { DefaultEventDispatcher } from './default_dispatcher'; -import { NodeRequestHandler } from '../../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../../utils/http_request_handler/request_handler.node'; import eventDispatcher from './default_dispatcher.node'; describe('eventDispatcher', () => { diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.node.ts b/lib/event_processor/event_dispatcher/default_dispatcher.node.ts index 52524140c..65dc115af 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.node.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.node.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { EventDispatcher } from './event_dispatcher'; -import { NodeRequestHandler } from '../../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../../utils/http_request_handler/request_handler.node'; import { DefaultEventDispatcher } from './default_dispatcher'; const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new NodeRequestHandler()); diff --git a/lib/index.lite.ts b/lib/index.lite.ts index a7cc7cf22..eae2a00e0 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -27,7 +27,7 @@ import * as enums from './utils/enums'; import * as loggerPlugin from './plugins/logger'; import Optimizely from './optimizely'; import { createNotificationCenter } from './notification_center'; -import { OptimizelyDecideOption, Client, ConfigLite } from './shared_types'; +import { OptimizelyDecideOption, Client, Config } from './shared_types'; import * as commonExports from './common_exports'; const logger = getLogger(); @@ -40,7 +40,7 @@ setLogLevel(LogLevel.ERROR); * @return {Client|null} the Optimizely client object * null on error */ - const createInstance = function(config: ConfigLite): Client | null { + const createInstance = function(config: Config): Client | null { try { // TODO warn about setting per instance errorHandler / logger / logLevel diff --git a/lib/index.node.ts b/lib/index.node.ts index 63f7e16e5..16605d246 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -73,7 +73,6 @@ const createInstance = function(config: Config): Client | null { } } - const errorHandler = getErrorHandler(); const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts new file mode 100644 index 000000000..64ca63520 --- /dev/null +++ b/lib/index.react_native.spec.ts @@ -0,0 +1,177 @@ +/** + * Copyright 2019-2020, 2022-2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; + +import * as logging from './modules/logging/logger'; + +import Optimizely from './optimizely'; +import testData from './tests/test_data'; +import packageJSON from '../package.json'; +import optimizelyFactory from './index.react_native'; +import configValidator from './utils/config_validator'; +import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { createProjectConfig } from './project_config/project_config'; + +vi.mock('@react-native-community/netinfo'); +vi.mock('react-native-get-random-values') +vi.mock('fast-text-encoding') + +describe('javascript-sdk/react-native', () => { + beforeEach(() => { + vi.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent'); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + describe('APIs', () => { + it('should expose logger, errorHandler, eventDispatcher and enums', () => { + expect(optimizelyFactory.logging).toBeDefined(); + expect(optimizelyFactory.logging.createLogger).toBeDefined(); + expect(optimizelyFactory.logging.createNoOpLogger).toBeDefined(); + expect(optimizelyFactory.errorHandler).toBeDefined(); + expect(optimizelyFactory.eventDispatcher).toBeDefined(); + expect(optimizelyFactory.enums).toBeDefined(); + }); + + describe('createInstance', () => { + const fakeErrorHandler = { handleError: function() {} }; + const fakeEventDispatcher = { dispatchEvent: async function() { + return Promise.resolve({}); + } }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + let silentLogger; + + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + silentLogger = optimizelyFactory.logging.createLogger(); + vi.spyOn(console, 'error'); + vi.spyOn(configValidator, 'validate').mockImplementation(() => { + throw new Error('Invalid config or something'); + }); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should not throw if the provided config is not valid', () => { + expect(function() { + const optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: getMockProjectConfigManager(), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + logger: silentLogger, + }); + }).not.toThrow(); + }); + + it('should create an instance of optimizely', () => { + const optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: getMockProjectConfigManager(), + errorHandler: fakeErrorHandler, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + logger: silentLogger, + }); + + expect(optlyInstance).toBeInstanceOf(Optimizely); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(optlyInstance.clientVersion).toEqual('5.3.4'); + }); + + it('should set the React Native JS client engine and javascript SDK version', () => { + const optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: getMockProjectConfigManager(), + errorHandler: fakeErrorHandler, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + logger: silentLogger, + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect('react-native-js-sdk').toEqual(optlyInstance.clientEngine); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(packageJSON.version).toEqual(optlyInstance.clientVersion); + }); + + it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { + const optlyInstance = optimizelyFactory.createInstance({ + clientEngine: 'react-sdk', + projectConfigManager: getMockProjectConfigManager(), + errorHandler: fakeErrorHandler, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + logger: silentLogger, + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect('react-native-sdk').toEqual(optlyInstance.clientEngine); + }); + + describe('when passing in logLevel', () => { + beforeEach(() => { + vi.spyOn(logging, 'setLogLevel'); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should call logging.setLogLevel', () => { + optimizelyFactory.createInstance({ + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), + logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, + }); + expect(logging.setLogLevel).toBeCalledTimes(1); + expect(logging.setLogLevel).toBeCalledWith(optimizelyFactory.enums.LOG_LEVEL.ERROR); + }); + }); + + describe('when passing in logger', () => { + beforeEach(() => { + vi.spyOn(logging, 'setLogHandler'); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should call logging.setLogHandler with the supplied logger', () => { + const fakeLogger = { log: function() {} }; + optimizelyFactory.createInstance({ + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + logger: fakeLogger, + }); + expect(logging.setLogHandler).toBeCalledTimes(1); + expect(logging.setLogHandler).toBeCalledWith(fakeLogger); + }); + }); + }); + }); +}); diff --git a/tests/logger.spec.ts b/lib/modules/logging/logger.spec.ts similarity index 98% rename from tests/logger.spec.ts rename to lib/modules/logging/logger.spec.ts index 17d5cc38b..0440755bb 100644 --- a/tests/logger.spec.ts +++ b/lib/modules/logging/logger.spec.ts @@ -4,7 +4,7 @@ import { LogLevel, LogHandler, LoggerFacade, -} from '../lib/modules/logging/models' +} from './models' import { setLogHandler, @@ -13,10 +13,10 @@ import { ConsoleLogHandler, resetLogger, getLogLevel, -} from '../lib/modules/logging/logger' +} from './logger' -import { resetErrorHandler } from '../lib/modules/logging/errorHandler' -import { ErrorHandler, setErrorHandler } from '../lib/modules/logging/errorHandler' +import { resetErrorHandler } from './errorHandler' +import { ErrorHandler, setErrorHandler } from './errorHandler' describe('logger', () => { afterEach(() => { @@ -302,6 +302,7 @@ describe('logger', () => { it('should set logLevel to ERROR when setLogLevel is called with no value', () => { const logger = new ConsoleLogHandler() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore logger.setLogLevel() diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index dfe8d496a..12d061918 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -22,7 +22,7 @@ import { exhaustMicrotasks } from '../../tests/testUtils'; import { OdpEvent } from './odp_event'; import { OdpConfig } from '../odp_config'; import { EventDispatchResponse } from './odp_event_api_manager'; -import { advanceTimersByTime } from '../../../tests/testUtils'; +import { advanceTimersByTime } from '../../tests/testUtils'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; diff --git a/lib/odp/odp_manager_factory.browser.spec.ts b/lib/odp/odp_manager_factory.browser.spec.ts index 333856743..534046f94 100644 --- a/lib/odp/odp_manager_factory.browser.spec.ts +++ b/lib/odp/odp_manager_factory.browser.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -vi.mock('../utils/http_request_handler/browser_request_handler', () => { +vi.mock('../utils/http_request_handler/request_handler.browser', () => { return { BrowserRequestHandler: vi.fn() }; }); @@ -26,7 +26,7 @@ vi.mock('./odp_manager_factory', () => { import { describe, it, expect, beforeEach, vi } from 'vitest'; import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; import { BROWSER_DEFAULT_API_TIMEOUT, createOdpManager } from './odp_manager_factory.browser'; -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; describe('createOdpManager', () => { diff --git a/lib/odp/odp_manager_factory.browser.ts b/lib/odp/odp_manager_factory.browser.ts index 481252278..f70dfe976 100644 --- a/lib/odp/odp_manager_factory.browser.ts +++ b/lib/odp/odp_manager_factory.browser.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { OdpManager } from './odp_manager'; import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; diff --git a/lib/odp/odp_manager_factory.node.spec.ts b/lib/odp/odp_manager_factory.node.spec.ts index b63850180..491fd7520 100644 --- a/lib/odp/odp_manager_factory.node.spec.ts +++ b/lib/odp/odp_manager_factory.node.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -vi.mock('../utils/http_request_handler/node_request_handler', () => { +vi.mock('../utils/http_request_handler/request_handler.node', () => { return { NodeRequestHandler: vi.fn() }; }); @@ -26,7 +26,7 @@ vi.mock('./odp_manager_factory', () => { import { describe, it, expect, beforeEach, vi } from 'vitest'; import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; import { NODE_DEFAULT_API_TIMEOUT, NODE_DEFAULT_BATCH_SIZE, NODE_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.node'; -import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; describe('createOdpManager', () => { diff --git a/lib/odp/odp_manager_factory.node.ts b/lib/odp/odp_manager_factory.node.ts index 3d449fd3b..f2438dbd9 100644 --- a/lib/odp/odp_manager_factory.node.ts +++ b/lib/odp/odp_manager_factory.node.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { OdpManager } from './odp_manager'; import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; diff --git a/lib/odp/odp_manager_factory.react_native.spec.ts b/lib/odp/odp_manager_factory.react_native.spec.ts index 604a71bc7..640e9cf4e 100644 --- a/lib/odp/odp_manager_factory.react_native.spec.ts +++ b/lib/odp/odp_manager_factory.react_native.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -vi.mock('../utils/http_request_handler/browser_request_handler', () => { +vi.mock('../utils/http_request_handler/request_handler.browser', () => { return { BrowserRequestHandler: vi.fn() }; }); @@ -26,7 +26,7 @@ vi.mock('./odp_manager_factory', () => { import { describe, it, expect, beforeEach, vi } from 'vitest'; import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; import { RN_DEFAULT_API_TIMEOUT, RN_DEFAULT_BATCH_SIZE, RN_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.react_native'; -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler' +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser' import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; describe('createOdpManager', () => { diff --git a/lib/odp/odp_manager_factory.react_native.ts b/lib/odp/odp_manager_factory.react_native.ts index c63982430..1ba0bcc5c 100644 --- a/lib/odp/odp_manager_factory.react_native.ts +++ b/lib/odp/odp_manager_factory.react_native.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { OdpManager } from './odp_manager'; import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; diff --git a/lib/plugins/key_value_cache/persistentKeyValueCache.ts b/lib/plugins/key_value_cache/persistentKeyValueCache.ts deleted file mode 100644 index f6b182738..000000000 --- a/lib/plugins/key_value_cache/persistentKeyValueCache.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys. - */ -export default interface PersistentKeyValueCache<V = string> { - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Returns value stored against a key or undefined if not found. - * @param key - * @returns - * Resolves promise with - * 1. object as value if found. - * 2. undefined if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<V | undefined>; - - /** - * Removes the key value pair from cache. - * @param key * - * @returns - * Resolves promise with - * 1. true if key-value was removed or - * 2. false if key not found - * Rejects the promise in case of an error - */ - remove(key: string): Promise<boolean>; - - /** - * Stores any object in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: V): Promise<void>; -} diff --git a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 9529595be..000000000 --- a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2020, 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import PersistentKeyValueCache from './persistentKeyValueCache'; -import { getDefaultAsyncStorage } from '../../utils/import.react_native/@react-native-async-storage/async-storage'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - private asyncStorage = getDefaultAsyncStorage(); - - async contains(key: string): Promise<boolean> { - return (await this.asyncStorage.getItem(key)) !== null; - } - - async get(key: string): Promise<string | undefined> { - return (await this.asyncStorage.getItem(key)) || undefined; - } - - async remove(key: string): Promise<boolean> { - if (await this.contains(key)) { - await this.asyncStorage.removeItem(key); - return true; - } - return false; - } - - set(key: string, val: string): Promise<void> { - return this.asyncStorage.setItem(key, val); - } -} diff --git a/lib/project_config/config_manager_factory.browser.spec.ts b/lib/project_config/config_manager_factory.browser.spec.ts index 7141cc16c..843111fb4 100644 --- a/lib/project_config/config_manager_factory.browser.spec.ts +++ b/lib/project_config/config_manager_factory.browser.spec.ts @@ -22,14 +22,14 @@ vi.mock('./config_manager_factory', () => { }; }); -vi.mock('../utils/http_request_handler/browser_request_handler', () => { +vi.mock('../utils/http_request_handler/request_handler.browser', () => { const BrowserRequestHandler = vi.fn(); return { BrowserRequestHandler }; }); import { getPollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.browser'; -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { diff --git a/lib/project_config/config_manager_factory.browser.ts b/lib/project_config/config_manager_factory.browser.ts index 8ae0bfd9e..8a5433bd5 100644 --- a/lib/project_config/config_manager_factory.browser.ts +++ b/lib/project_config/config_manager_factory.browser.ts @@ -15,7 +15,7 @@ */ import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { ProjectConfigManager } from './project_config_manager'; export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { diff --git a/lib/project_config/config_manager_factory.node.spec.ts b/lib/project_config/config_manager_factory.node.spec.ts index 6ef8e04e0..41dedd4f5 100644 --- a/lib/project_config/config_manager_factory.node.spec.ts +++ b/lib/project_config/config_manager_factory.node.spec.ts @@ -22,14 +22,14 @@ vi.mock('./config_manager_factory', () => { }; }); -vi.mock('../utils/http_request_handler/node_request_handler', () => { +vi.mock('../utils/http_request_handler/request_handler.node', () => { const NodeRequestHandler = vi.fn(); return { NodeRequestHandler }; }); import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.node'; -import { NodeRequestHandler } from '../utils/http_request_handler/node_request_handler'; +import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { diff --git a/lib/project_config/config_manager_factory.node.ts b/lib/project_config/config_manager_factory.node.ts index 7a220bc12..241927c5a 100644 --- a/lib/project_config/config_manager_factory.node.ts +++ b/lib/project_config/config_manager_factory.node.ts @@ -15,7 +15,7 @@ */ import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; -import { NodeRequestHandler } from "../utils/http_request_handler/node_request_handler"; +import { NodeRequestHandler } from "../utils/http_request_handler/request_handler.node"; import { ProjectConfigManager } from "./project_config_manager"; import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './constant'; diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index b047af03a..e688c588a 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -42,7 +42,7 @@ vi.mock('./config_manager_factory', () => { }; }); -vi.mock('../utils/http_request_handler/browser_request_handler', () => { +vi.mock('../utils/http_request_handler/request_handler.browser', () => { const BrowserRequestHandler = vi.fn(); return { BrowserRequestHandler }; }); @@ -58,7 +58,7 @@ vi.mock('../utils/cache/async_storage_cache.react_native', async (importOriginal import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.react_native'; -import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { getMockSyncCache } from '../tests/mock/mock_cache'; diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts index 17e71f045..6edcbbe3f 100644 --- a/lib/project_config/config_manager_factory.react_native.ts +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -15,7 +15,7 @@ */ import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; -import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; +import { BrowserRequestHandler } from "../utils/http_request_handler/request_handler.browser"; import { ProjectConfigManager } from "./project_config_manager"; import { AsyncStorageCache } from "../utils/cache/async_storage_cache.react_native"; diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts index 967aec83c..682c2bede 100644 --- a/lib/project_config/project_config_manager.spec.ts +++ b/lib/project_config/project_config_manager.spec.ts @@ -21,7 +21,7 @@ import * as testData from '../tests/test_data'; import { createProjectConfig } from './project_config'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { getMockDatafileManager } from '../tests/mock/mock_datafile_manager'; -import { wait } from '../../tests/testUtils'; +import { wait } from '../tests/testUtils'; const cloneDeep = (x: any) => JSON.parse(JSON.stringify(x)); diff --git a/lib/shared_types.ts b/lib/shared_types.ts index fa3579e69..b2ebad540 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -32,7 +32,6 @@ import { OdpSegmentManager } from './odp/segment_manager/odp_segment_manager'; import { DefaultOdpEventApiManager } from './odp/event_manager/odp_event_api_manager'; import { OdpEventManager } from './odp/event_manager/odp_event_manager'; import { OdpManager } from './odp/odp_manager'; -import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; import { ProjectConfig } from './project_config/project_config'; import { ProjectConfigManager } from './project_config/project_config_manager'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; @@ -359,25 +358,11 @@ export interface TrackListenerPayload extends ListenerPayload { logEvent: Event; } -export type PersistentCacheProvider = () => PersistentCache; - /** * Entry level Config Entities * For compatibility with the previous declaration file */ -export interface Config extends ConfigLite { - // eventBatchSize?: number; // Maximum size of events to be dispatched in a batch - // eventFlushInterval?: number; // Maximum time for an event to be enqueued - // eventMaxQueueSize?: number; // Maximum size for the event queue - sdkKey?: string; - persistentCacheProvider?: PersistentCacheProvider; -} - -/** - * Entry level Config Entities for Lite bundle - * For compatibility with the previous declaration file - */ -export interface ConfigLite { +export interface Config { projectConfigManager: ProjectConfigManager; // errorHandler object for logging error errorHandler?: ErrorHandler; diff --git a/lib/tests/testUtils.ts b/lib/tests/testUtils.ts index 8bcd093f8..2a4cbe3c5 100644 --- a/lib/tests/testUtils.ts +++ b/lib/tests/testUtils.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { vi } from 'vitest'; export const exhaustMicrotasks = async (loop = 100): Promise<void> => { for(let i = 0; i < loop; i++) { @@ -21,3 +22,9 @@ export const exhaustMicrotasks = async (loop = 100): Promise<void> => { }; export const wait = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms)); + +export const advanceTimersByTime = (waitMs: number): Promise<void> => { + const timeoutPromise: Promise<void> = new Promise(res => setTimeout(res, waitMs)); + vi.advanceTimersByTime(waitMs); + return timeoutPromise; +} diff --git a/lib/utils/executor/backoff_retry_runner.spec.ts b/lib/utils/executor/backoff_retry_runner.spec.ts index 6e2674b10..a1dd1f3a3 100644 --- a/lib/utils/executor/backoff_retry_runner.spec.ts +++ b/lib/utils/executor/backoff_retry_runner.spec.ts @@ -1,6 +1,6 @@ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; import { runWithRetry } from './backoff_retry_runner'; -import { advanceTimersByTime } from '../../../tests/testUtils'; +import { advanceTimersByTime } from '../../tests/testUtils'; const exhaustMicrotasks = async (loop = 100) => { for(let i = 0; i < loop; i++) { diff --git a/tests/utils.spec.ts b/lib/utils/fns/index.spec.ts similarity index 98% rename from tests/utils.spec.ts rename to lib/utils/fns/index.spec.ts index aa529e241..6f93c6ac6 100644 --- a/tests/utils.spec.ts +++ b/lib/utils/fns/index.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { isValidEnum, groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '../lib/utils/fns' +import { isValidEnum, groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '.' describe('utils', () => { describe('isValidEnum', () => { diff --git a/tests/browserRequestHandler.spec.ts b/lib/utils/http_request_handler/request_handler.browser.spec.ts similarity index 96% rename from tests/browserRequestHandler.spec.ts rename to lib/utils/http_request_handler/request_handler.browser.spec.ts index f28ee1f26..0bb0d98ed 100644 --- a/tests/browserRequestHandler.spec.ts +++ b/lib/utils/http_request_handler/request_handler.browser.spec.ts @@ -17,8 +17,8 @@ import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; -import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; -import { NoOpLogger } from '../lib/plugins/logger'; +import { BrowserRequestHandler } from './request_handler.browser'; +import { NoOpLogger } from '../../plugins/logger'; describe('BrowserRequestHandler', () => { const host = 'https://endpoint.example.com/api/query'; diff --git a/lib/utils/http_request_handler/browser_request_handler.ts b/lib/utils/http_request_handler/request_handler.browser.ts similarity index 100% rename from lib/utils/http_request_handler/browser_request_handler.ts rename to lib/utils/http_request_handler/request_handler.browser.ts diff --git a/tests/nodeRequestHandler.spec.ts b/lib/utils/http_request_handler/request_handler.node.spec.ts similarity index 98% rename from tests/nodeRequestHandler.spec.ts rename to lib/utils/http_request_handler/request_handler.node.spec.ts index 9bcc0d813..ef10fbc21 100644 --- a/tests/nodeRequestHandler.spec.ts +++ b/lib/utils/http_request_handler/request_handler.node.spec.ts @@ -18,8 +18,8 @@ import { describe, beforeEach, afterEach, beforeAll, afterAll, it, vi, expect } import nock from 'nock'; import zlib from 'zlib'; -import { NodeRequestHandler } from '../lib/utils/http_request_handler/node_request_handler'; -import { NoOpLogger } from '../lib/plugins/logger'; +import { NodeRequestHandler } from './request_handler.node'; +import { NoOpLogger } from '../../plugins/logger'; beforeAll(() => { nock.disableNetConnect(); diff --git a/lib/utils/http_request_handler/node_request_handler.ts b/lib/utils/http_request_handler/request_handler.node.ts similarity index 100% rename from lib/utils/http_request_handler/node_request_handler.ts rename to lib/utils/http_request_handler/request_handler.node.ts diff --git a/lib/utils/repeater/repeater.spec.ts b/lib/utils/repeater/repeater.spec.ts index 7d998e7b6..e92594556 100644 --- a/lib/utils/repeater/repeater.spec.ts +++ b/lib/utils/repeater/repeater.spec.ts @@ -15,7 +15,7 @@ */ import { expect, vi, it, beforeEach, afterEach, describe } from 'vitest'; import { ExponentialBackoff, IntervalRepeater } from './repeater'; -import { advanceTimersByTime } from '../../../tests/testUtils'; +import { advanceTimersByTime } from '../../tests/testUtils'; import { resolvablePromise } from '../promise/resolvablePromise'; describe("ExponentialBackoff", () => { diff --git a/tests/index.react_native.spec.ts b/tests/index.react_native.spec.ts deleted file mode 100644 index a5fab6aff..000000000 --- a/tests/index.react_native.spec.ts +++ /dev/null @@ -1,346 +0,0 @@ -/** - * Copyright 2019-2020, 2022-2024 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; - -import * as logging from '../lib/modules/logging/logger'; - -import Optimizely from '../lib/optimizely'; -import testData from '../lib/tests/test_data'; -import packageJSON from '../package.json'; -import optimizelyFactory from '../lib/index.react_native'; -import configValidator from '../lib/utils/config_validator'; -import { getMockProjectConfigManager } from '../lib/tests/mock/mock_project_config_manager'; -import { createProjectConfig } from '../lib/project_config/project_config'; - -vi.mock('@react-native-community/netinfo'); -vi.mock('react-native-get-random-values') -vi.mock('fast-text-encoding') - -describe('javascript-sdk/react-native', () => { - beforeEach(() => { - vi.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent'); - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - describe('APIs', () => { - it('should expose logger, errorHandler, eventDispatcher and enums', () => { - expect(optimizelyFactory.logging).toBeDefined(); - expect(optimizelyFactory.logging.createLogger).toBeDefined(); - expect(optimizelyFactory.logging.createNoOpLogger).toBeDefined(); - expect(optimizelyFactory.errorHandler).toBeDefined(); - expect(optimizelyFactory.eventDispatcher).toBeDefined(); - expect(optimizelyFactory.enums).toBeDefined(); - }); - - describe('createInstance', () => { - const fakeErrorHandler = { handleError: function() {} }; - const fakeEventDispatcher = { dispatchEvent: async function() { - return Promise.resolve({}); - } }; - // @ts-ignore - let silentLogger; - - beforeEach(() => { - // @ts-ignore - silentLogger = optimizelyFactory.logging.createLogger(); - vi.spyOn(console, 'error'); - vi.spyOn(configValidator, 'validate').mockImplementation(() => { - throw new Error('Invalid config or something'); - }); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should not throw if the provided config is not valid', () => { - expect(function() { - const optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - // @ts-ignore - logger: silentLogger, - }); - }).not.toThrow(); - }); - - it('should create an instance of optimizely', () => { - const optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - // @ts-ignore - logger: silentLogger, - }); - - expect(optlyInstance).toBeInstanceOf(Optimizely); - // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.3.4'); - }); - - it('should set the React Native JS client engine and javascript SDK version', () => { - const optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - // @ts-ignore - logger: silentLogger, - }); - - // @ts-ignore - expect('react-native-js-sdk').toEqual(optlyInstance.clientEngine); - // @ts-ignore - expect(packageJSON.version).toEqual(optlyInstance.clientVersion); - }); - - it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { - const optlyInstance = optimizelyFactory.createInstance({ - clientEngine: 'react-sdk', - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - // @ts-ignore - logger: silentLogger, - }); - // @ts-ignore - expect('react-native-sdk').toEqual(optlyInstance.clientEngine); - }); - - describe('when passing in logLevel', () => { - beforeEach(() => { - vi.spyOn(logging, 'setLogLevel'); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should call logging.setLogLevel', () => { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, - }); - expect(logging.setLogLevel).toBeCalledTimes(1); - expect(logging.setLogLevel).toBeCalledWith(optimizelyFactory.enums.LOG_LEVEL.ERROR); - }); - }); - - describe('when passing in logger', () => { - beforeEach(() => { - vi.spyOn(logging, 'setLogHandler'); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should call logging.setLogHandler with the supplied logger', () => { - const fakeLogger = { log: function() {} }; - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - // @ts-ignore - logger: fakeLogger, - }); - expect(logging.setLogHandler).toBeCalledTimes(1); - expect(logging.setLogHandler).toBeCalledWith(fakeLogger); - }); - }); - - // TODO: user will create and inject an event processor - // these tests will be refactored accordingly - // describe('event processor configuration', () => { - // // @ts-ignore - // let eventProcessorSpy; - // beforeEach(() => { - // eventProcessorSpy = vi.spyOn(eventProcessor, 'createEventProcessor'); - // }); - - // afterEach(() => { - // vi.resetAllMocks(); - // }); - - // it('should use default event flush interval when none is provided', () => { - // optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - // }), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // // @ts-ignore - // logger: silentLogger, - // }); - - // expect( - // // @ts-ignore - // eventProcessorSpy - // ).toBeCalledWith( - // expect.objectContaining({ - // flushInterval: 1000, - // }) - // ); - // }); - - // describe('with an invalid flush interval', () => { - // beforeEach(() => { - // vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); - // }); - - // afterEach(() => { - // vi.resetAllMocks(); - // }); - - // it('should ignore the event flush interval and use the default instead', () => { - // optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - // }), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // // @ts-ignore - // logger: silentLogger, - // // @ts-ignore - // eventFlushInterval: ['invalid', 'flush', 'interval'], - // }); - // expect( - // // @ts-ignore - // eventProcessorSpy - // ).toBeCalledWith( - // expect.objectContaining({ - // flushInterval: 1000, - // }) - // ); - // }); - // }); - - // describe('with a valid flush interval', () => { - // beforeEach(() => { - // vi.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); - // }); - - // afterEach(() => { - // vi.resetAllMocks(); - // }); - - // it('should use the provided event flush interval', () => { - // optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - // }), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // // @ts-ignore - // logger: silentLogger, - // eventFlushInterval: 9000, - // }); - // expect( - // // @ts-ignore - // eventProcessorSpy - // ).toBeCalledWith( - // expect.objectContaining({ - // flushInterval: 9000, - // }) - // ); - // }); - // }); - - // it('should use default event batch size when none is provided', () => { - // optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - // }), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // // @ts-ignore - // logger: silentLogger, - // }); - // expect( - // // @ts-ignore - // eventProcessorSpy - // ).toBeCalledWith( - // expect.objectContaining({ - // batchSize: 10, - // }) - // ); - // }); - - // describe('with an invalid event batch size', () => { - // beforeEach(() => { - // vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); - // }); - - // afterEach(() => { - // vi.resetAllMocks(); - // }); - - // it('should ignore the event batch size and use the default instead', () => { - // optimizelyFactory.createInstance({ - // datafile: testData.getTestProjectConfigWithFeatures(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // // @ts-ignore - // logger: silentLogger, - // // @ts-ignore - // eventBatchSize: null, - // }); - // expect( - // // @ts-ignore - // eventProcessorSpy - // ).toBeCalledWith( - // expect.objectContaining({ - // batchSize: 10, - // }) - // ); - // }); - // }); - - // describe('with a valid event batch size', () => { - // beforeEach(() => { - // vi.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); - // }); - - // afterEach(() => { - // vi.resetAllMocks(); - // }); - - // it('should use the provided event batch size', () => { - // optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), - // }), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // // @ts-ignore - // logger: silentLogger, - // eventBatchSize: 300, - // }); - // expect( - // // @ts-ignore - // eventProcessorSpy - // ).toBeCalledWith( - // expect.objectContaining({ - // batchSize: 300, - // }) - // ); - // }); - // }); - // }); - }); - }); -}); diff --git a/tests/reactNativeAsyncStorageCache.spec.ts b/tests/reactNativeAsyncStorageCache.spec.ts deleted file mode 100644 index 559c1f071..000000000 --- a/tests/reactNativeAsyncStorageCache.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2020, 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeEach, it, vi, expect } from 'vitest'; -import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; - -vi.mock('@react-native-async-storage/async-storage') - -describe('ReactNativeAsyncStorageCache', () => { - const TEST_OBJECT_KEY = 'testObject'; - const testObject = { name: 'An object', with: { some: 2, properties: ['one', 'two'] } }; - let cacheInstance: ReactNativeAsyncStorageCache; - - beforeEach(() => { - cacheInstance = new ReactNativeAsyncStorageCache(); - cacheInstance.set(TEST_OBJECT_KEY, JSON.stringify(testObject)); - }); - - describe('contains', () => { - it('should return true if value with key exists', async () => { - const keyWasFound = await cacheInstance.contains(TEST_OBJECT_KEY); - - expect(keyWasFound).toBe(true); - }); - - it('should return false if value with key does not exist', async () => { - const keyWasFound = await cacheInstance.contains('keyThatDoesNotExist'); - - expect(keyWasFound).toBe(false); - }); - }); - - describe('get', () => { - it('should return correct string when item is found in cache', async () => { - const json = await cacheInstance.get(TEST_OBJECT_KEY); - const parsedObject = JSON.parse(json ?? ''); - - expect(parsedObject).toEqual(testObject); - }); - - it('should return undefined if item is not found in cache', async () => { - const json = await cacheInstance.get('keyThatDoesNotExist'); - - expect(json).toBeUndefined(); - }); - }); - - describe('remove', () => { - it('should return true after removing a found entry', async () => { - const wasSuccessful = await cacheInstance.remove(TEST_OBJECT_KEY); - - expect(wasSuccessful).toBe(true); - }); - - it('should return false after trying to remove an entry that is not found ', async () => { - const wasSuccessful = await cacheInstance.remove('keyThatDoesNotExist'); - - expect(wasSuccessful).toBe(false); - }); - }); -}); diff --git a/tests/testUtils.ts b/tests/testUtils.ts deleted file mode 100644 index 118e3e78c..000000000 --- a/tests/testUtils.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { vi } from 'vitest'; - -import PersistentKeyValueCache from "../lib/plugins/key_value_cache/persistentKeyValueCache"; - -export function advanceTimersByTime(waitMs: number): Promise<void> { - const timeoutPromise: Promise<void> = new Promise(res => setTimeout(res, waitMs)); - vi.advanceTimersByTime(waitMs); - return timeoutPromise; -} - -export function getTimerCount(): number { - return vi.getTimerCount(); -} - -export const getTestPersistentCache = (): PersistentKeyValueCache => { - const cache = { - get: vi.fn().mockImplementation((key: string): Promise<string | undefined> => { - let val : string | undefined = undefined; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); - }), - - set: vi.fn().mockImplementation((): Promise<void> => { - return Promise.resolve(); - }), - - contains: vi.fn().mockImplementation((): Promise<boolean> => { - return Promise.resolve(false); - }), - - remove: vi.fn().mockImplementation((): Promise<boolean> => { - return Promise.resolve(false); - }), - }; - - return cache; -} - -export const wait = (ms: number) => { - return new Promise((resolve) => setTimeout(resolve, ms)); -}; From 1adcbdb8dc435afb5583b150678d1112d941c32b Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 25 Dec 2024 18:21:29 +0600 Subject: [PATCH 107/200] [FSSDK-11006] remove assign function and use spreading (#979) --- lib/core/audience_evaluator/index.ts | 5 ++-- lib/core/decision_service/index.ts | 2 +- lib/project_config/project_config.ts | 34 +++++++++++++--------- lib/utils/fns/index.tests.js | 17 ----------- lib/utils/fns/index.ts | 25 ---------------- lib/utils/local_storage/tryLocalStorage.ts | 32 -------------------- 6 files changed, 25 insertions(+), 90 deletions(-) delete mode 100644 lib/utils/local_storage/tryLocalStorage.ts diff --git a/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts index 550694610..b39cacb8a 100644 --- a/lib/core/audience_evaluator/index.ts +++ b/lib/core/audience_evaluator/index.ts @@ -44,10 +44,11 @@ export class AudienceEvaluator { * @constructor */ constructor(UNSTABLE_conditionEvaluators: unknown) { - this.typeToEvaluatorMap = fns.assign({}, UNSTABLE_conditionEvaluators, { + this.typeToEvaluatorMap = { + ...UNSTABLE_conditionEvaluators as any, custom_attribute: customAttributeConditionEvaluator, third_party_dimension: odpSegmentsConditionEvaluator, - }); + }; } /** diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 16718fe7e..5522e3905 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -312,7 +312,7 @@ export class DecisionService { const userProfile = this.getUserProfile(userId) || {} as UserProfile; const attributeExperimentBucketMap = attributes[CONTROL_ATTRIBUTES.STICKY_BUCKETING_KEY]; - return fns.assign({}, userProfile.experiment_bucket_map, attributeExperimentBucketMap); + return { ...userProfile.experiment_bucket_map, ...attributeExperimentBucketMap as any }; } /** diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 2f9de78ff..a9b618894 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { find, objectEntries, objectValues, sprintf, assign, keyBy } from '../utils/fns'; +import { find, objectEntries, objectValues, sprintf, keyBy } from '../utils/fns'; import { ERROR_MESSAGES, LOG_LEVEL, LOG_MESSAGES, FEATURE_VARIABLE_TYPES } from '../utils/enums'; import configValidator from '../utils/config_validator'; @@ -99,27 +99,27 @@ const MODULE_NAME = 'PROJECT_CONFIG'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { - const datafileCopy = assign({}, datafile); + const datafileCopy = { ...datafile }; datafileCopy.audiences = (datafile.audiences || []).map((audience: Audience) => { - return assign({}, audience); + return { ...audience }; }); datafileCopy.experiments = (datafile.experiments || []).map((experiment: Experiment) => { - return assign({}, experiment); + return { ...experiment }; }); datafileCopy.featureFlags = (datafile.featureFlags || []).map((featureFlag: FeatureFlag) => { - return assign({}, featureFlag); + return { ...featureFlag }; }); datafileCopy.groups = (datafile.groups || []).map((group: Group) => { - const groupCopy = assign({}, group); + const groupCopy = { ...group }; groupCopy.experiments = (group.experiments || []).map(experiment => { - return assign({}, experiment); + return { ...experiment }; }); return groupCopy; }); datafileCopy.rollouts = (datafile.rollouts || []).map((rollout: Rollout) => { - const rolloutCopy = assign({}, rollout); + const rolloutCopy = { ...rollout }; rolloutCopy.experiments = (rollout.experiments || []).map(experiment => { - return assign({}, experiment); + return { ...experiment }; }); return rolloutCopy; }); @@ -148,8 +148,11 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str (projectConfig.audiences || []).forEach(audience => { audience.conditions = JSON.parse(audience.conditions as string); }); - projectConfig.audiencesById = keyBy(projectConfig.audiences, 'id'); - assign(projectConfig.audiencesById, keyBy(projectConfig.typedAudiences, 'id')); + + projectConfig.audiencesById = { + ...keyBy(projectConfig.audiences, 'id'), + ...keyBy(projectConfig.typedAudiences, 'id'), + } projectConfig.attributeKeyMap = keyBy(projectConfig.attributes, 'key'); projectConfig.eventKeyMap = keyBy(projectConfig.events, 'key'); @@ -159,7 +162,8 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str Object.keys(projectConfig.groupIdMap || {}).forEach(Id => { experiments = projectConfig.groupIdMap[Id].experiments; (experiments || []).forEach(experiment => { - projectConfig.experiments.push(assign(experiment, { groupId: Id })); + experiment.groupId = Id; + projectConfig.experiments.push(experiment); }); }); @@ -226,7 +230,11 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str experiment.variationKeyMap = keyBy(experiment.variations, 'key'); // Creates { <variationId>: { key: <variationKey>, id: <variationId> } } mapping for quick lookup - assign(projectConfig.variationIdMap, keyBy(experiment.variations, 'id')); + projectConfig.variationIdMap = { + ...projectConfig.variationIdMap, + ...keyBy(experiment.variations, 'id') + }; + objectValues(experiment.variationKeyMap || {}).forEach(variation => { if (variation.variables) { projectConfig.variationVariableUsageMap[variation.id] = keyBy(variation.variables, 'id'); diff --git a/lib/utils/fns/index.tests.js b/lib/utils/fns/index.tests.js index 0d07bdc16..f2be54fea 100644 --- a/lib/utils/fns/index.tests.js +++ b/lib/utils/fns/index.tests.js @@ -84,22 +84,5 @@ describe('lib/utils/fns', function() { assert.isFalse(fns.isNumber(null)); }); }); - - describe('assign', function() { - it('should return empty object when target is not provided', function() { - assert.deepEqual(fns.assign(), {}); - }); - - it('should copy correctly when Object.assign is available in environment', function() { - assert.deepEqual(fns.assign({ a: 'a'}, {b: 'b'}), { a: 'a', b: 'b' }); - }); - - it('should copy correctly when Object.assign is not available in environment', function() { - var originalAssign = Object.assign; - Object.assign = null; - assert.deepEqual(fns.assign({ a: 'a'}, {b: 'b'}, {c: 'c'}), { a: 'a', b: 'b', c: 'c' }); - Object.assign = originalAssign; - }); - }); }); }); diff --git a/lib/utils/fns/index.ts b/lib/utils/fns/index.ts index e53402a22..e7ea3d071 100644 --- a/lib/utils/fns/index.ts +++ b/lib/utils/fns/index.ts @@ -17,30 +17,6 @@ import { v4 } from 'uuid'; const MAX_SAFE_INTEGER_LIMIT = Math.pow(2, 53); -// eslint-disable-next-line -export function assign(target: any, ...sources: any[]): any { - if (!target) { - return {}; - } - if (typeof Object.assign === 'function') { - return Object.assign(target, ...sources); - } else { - const to = Object(target); - for (let index = 0; index < sources.length; index++) { - const nextSource = sources[index]; - if (nextSource !== null && nextSource !== undefined) { - for (const nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - } -} - export function currentTimestamp(): number { return Math.round(new Date().getTime()); } @@ -165,7 +141,6 @@ export function checkArrayEquality(arrayA: string[], arrayB: string[]): boolean } export default { - assign, checkArrayEquality, currentTimestamp, isSafeInteger, diff --git a/lib/utils/local_storage/tryLocalStorage.ts b/lib/utils/local_storage/tryLocalStorage.ts deleted file mode 100644 index 252cbf8e7..000000000 --- a/lib/utils/local_storage/tryLocalStorage.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2023, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Checks to see if browser localStorage available. If so, runs and returns browserCallback. Otherwise, runs and returns nonBrowserCallback. - * @param {object} callbacks - * @param {[object.browserCallback]} callbacks.browserCallback - * @param {[object.nonBrowserCallback]} callbacks.nonBrowserCallback - * @returns - */ -export const tryWithLocalStorage = <K>({ - browserCallback, - nonBrowserCallback, -}: { - browserCallback: (localStorage?: Storage) => K; - nonBrowserCallback: () => K; -}): K => { - return typeof window !== 'undefined' ? browserCallback(window?.localStorage) : nonBrowserCallback(); -}; From 51e8c1a434172ec7fb13f73ad965fccafce480e2 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:48:23 +0600 Subject: [PATCH 108/200] [FSSDK-10935] Refactor log object export (#976) --- lib/core/audience_evaluator/index.ts | 12 +- .../index.tests.js | 8 +- .../odp_segment_condition_evaluator/index.ts | 5 +- lib/core/bucketer/index.tests.js | 43 +-- lib/core/bucketer/index.ts | 39 +-- .../index.tests.js | 63 +++-- .../index.ts | 41 +-- lib/core/decision_service/index.ts | 177 +++++++----- lib/error_messages.ts | 107 ++++++++ lib/event_processor/batch_event_processor.ts | 8 +- .../event_dispatcher/default_dispatcher.ts | 3 +- .../send_beacon_dispatcher.browser.ts | 3 +- ...ent_processor_factory.react_native.spec.ts | 7 +- .../forwarding_event_processor.ts | 3 +- lib/exception_messages.ts | 43 +++ lib/index.browser.tests.js | 6 +- lib/index.browser.ts | 4 +- lib/index.browser.umdtests.js | 3 +- lib/index.lite.tests.js | 3 +- lib/index.node.tests.js | 3 +- lib/index.node.ts | 1 + lib/index.react_native.ts | 1 + lib/log_messages.ts | 122 +++++++++ lib/notification_center/index.ts | 4 +- .../odp_event_api_manager.spec.ts | 4 +- .../event_manager/odp_event_manager.spec.ts | 5 +- lib/odp/event_manager/odp_event_manager.ts | 29 +- lib/odp/odp_manager.spec.ts | 7 +- lib/odp/odp_manager.ts | 3 +- .../odp_segment_api_manager.spec.ts | 2 +- .../segment_manager/odp_segment_manager.ts | 6 +- lib/optimizely/index.spec.ts | 1 - lib/optimizely/index.tests.js | 251 ++++++++++-------- lib/optimizely/index.ts | 129 +++++---- lib/optimizely_user_context/index.tests.js | 27 +- ...onfig_manager_factory.react_native.spec.ts | 4 +- .../polling_datafile_manager.ts | 31 ++- lib/project_config/project_config.tests.js | 13 +- lib/project_config/project_config.ts | 52 ++-- .../project_config_manager.spec.ts | 2 +- lib/project_config/project_config_manager.ts | 17 +- lib/utils/attributes_validator/index.tests.js | 10 +- lib/utils/attributes_validator/index.ts | 6 +- lib/utils/config_validator/index.tests.js | 21 +- lib/utils/config_validator/index.ts | 24 +- lib/utils/enums/index.ts | 181 +------------ lib/utils/event_tag_utils/index.ts | 17 +- lib/utils/event_tags_validator/index.tests.js | 8 +- lib/utils/event_tags_validator/index.ts | 5 +- lib/utils/executor/backoff_retry_runner.ts | 3 +- .../request_handler.browser.ts | 8 +- .../request_handler.node.ts | 12 +- .../async-storage.ts | 4 +- .../json_schema_validator/index.tests.js | 4 +- lib/utils/json_schema_validator/index.ts | 8 +- lib/utils/semantic_version/index.ts | 11 +- .../index.tests.js | 11 +- .../user_profile_service_validator/index.ts | 9 +- lib/vuid/vuid_manager_factory.node.spec.ts | 3 +- lib/vuid/vuid_manager_factory.node.ts | 3 +- message_generator.ts | 2 +- package.json | 2 +- 62 files changed, 966 insertions(+), 678 deletions(-) create mode 100644 lib/error_messages.ts create mode 100644 lib/exception_messages.ts create mode 100644 lib/log_messages.ts diff --git a/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts index b39cacb8a..5bb9f5a15 100644 --- a/lib/core/audience_evaluator/index.ts +++ b/lib/core/audience_evaluator/index.ts @@ -18,13 +18,13 @@ import { getLogger } from '../../modules/logging'; import fns from '../../utils/fns'; import { LOG_LEVEL, - LOG_MESSAGES, - ERROR_MESSAGES, } from '../../utils/enums'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; import * as odpSegmentsConditionEvaluator from './odp_segment_condition_evaluator'; import { Audience, Condition, OptimizelyUserContext } from '../../shared_types'; +import { CONDITION_EVALUATOR_ERROR, UNKNOWN_CONDITION_TYPE } from '../../error_messages'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE} from '../../log_messages'; const logger = getLogger(); const MODULE_NAME = 'AUDIENCE_EVALUATOR'; @@ -79,14 +79,14 @@ export class AudienceEvaluator { if (audience) { logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions) + EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions) ); const result = conditionTreeEvaluator.evaluate( audience.conditions as unknown[] , this.evaluateConditionWithUserAttributes.bind(this, user) ); const resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); - logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText); + logger.log(LOG_LEVEL.DEBUG, AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText); return result; } return null; @@ -105,7 +105,7 @@ export class AudienceEvaluator { evaluateConditionWithUserAttributes(user: OptimizelyUserContext, condition: Condition): boolean | null { const evaluator = this.typeToEvaluatorMap[condition.type]; if (!evaluator) { - logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition)); + logger.log(LOG_LEVEL.WARNING, UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition)); return null; } try { @@ -113,7 +113,7 @@ export class AudienceEvaluator { } catch (err: any) { logger.log( LOG_LEVEL.ERROR, - ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message + CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message ); } diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js index 503017545..768484b24 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js @@ -17,12 +17,10 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { sprintf } from '../../../utils/fns'; -import { - LOG_LEVEL, - LOG_MESSAGES, -} from '../../../utils/enums'; +import { LOG_LEVEL } from '../../../utils/enums'; import * as logging from '../../../modules/logging'; import * as odpSegmentEvalutor from './'; +import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; var odpSegment1Condition = { "value": "odp-segment-1", @@ -68,6 +66,6 @@ describe('lib/core/audience_evaluator/odp_segment_condition_evaluator', function sinon.assert.calledOnce(stubLogHandler.log); assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, 'ODP_SEGMENT_CONDITION_EVALUATOR', JSON.stringify(invalidOdpMatchCondition))); + assert.strictEqual(logMessage, sprintf(UNKNOWN_MATCH_TYPE, 'ODP_SEGMENT_CONDITION_EVALUATOR', JSON.stringify(invalidOdpMatchCondition))); }); }); diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts index 3098ae2b0..54d7b5d93 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ +import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; import { getLogger } from '../../../modules/logging'; import { Condition, OptimizelyUserContext } from '../../../shared_types'; -import { LOG_MESSAGES } from '../../../utils/enums'; - const MODULE_NAME = 'ODP_SEGMENT_CONDITION_EVALUATOR'; const logger = getLogger(); @@ -45,7 +44,7 @@ EVALUATORS_BY_MATCH_TYPE[QUALIFIED_MATCH_TYPE] = qualifiedEvaluator; export function evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { const conditionMatch = condition.match; if (typeof conditionMatch !== 'undefined' && MATCH_TYPES.indexOf(conditionMatch) === -1) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); + logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); return null; } diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index e30c9129e..eb4ec87eb 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -19,14 +19,17 @@ import { cloneDeep } from 'lodash'; import { sprintf } from '../../utils/fns'; import * as bucketer from './'; -import { - ERROR_MESSAGES, - LOG_MESSAGES, - LOG_LEVEL, -} from '../../utils/enums'; +import { LOG_LEVEL } from '../../utils/enums'; import { createLogger } from '../../plugins/logger'; import projectConfig from '../../project_config/project_config'; import { getTestProjectConfig } from '../../tests/test_data'; +import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; +import { + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_IN_ANY_EXPERIMENT, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, +} from '.'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var testData = getTestProjectConfig(); @@ -78,7 +81,7 @@ describe('lib/core/bucketer', function () { var bucketedUser_log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); expect(bucketedUser_log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'ppid1') + sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'ppid1') ); var bucketerParamsTest2 = cloneDeep(bucketerParams); @@ -88,7 +91,7 @@ describe('lib/core/bucketer', function () { var notBucketedUser_log1 = buildLogMessageFromArgs(createdLogger.log.args[1]); expect(notBucketedUser_log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'ppid2') + sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'ppid2') ); }); }); @@ -140,13 +143,13 @@ describe('lib/core/bucketer', function () { var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') + sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') ); var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); expect(log2).to.equal( sprintf( - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'BUCKETER', 'testUser', 'groupExperiment1', @@ -156,7 +159,7 @@ describe('lib/core/bucketer', function () { var log3 = buildLogMessageFromArgs(createdLogger.log.args[2]); expect(log3).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') + sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') ); }); @@ -171,12 +174,12 @@ describe('lib/core/bucketer', function () { var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '5000', 'testUser') + sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '5000', 'testUser') ); var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); expect(log2).to.equal( sprintf( - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'BUCKETER', 'testUser', 'groupExperiment1', @@ -196,10 +199,10 @@ describe('lib/core/bucketer', function () { var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'testUser') + sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'testUser') ); var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal(sprintf(LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); + expect(log2).to.equal(sprintf(USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); }); it('should return decision response with variation null when a user is bucketed into traffic space of deleted experiment within a random group', function () { @@ -213,10 +216,10 @@ describe('lib/core/bucketer', function () { var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '9000', 'testUser') + sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '9000', 'testUser') ); var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal(sprintf(LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); + expect(log2).to.equal(sprintf(USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); }); it('should throw an error if group ID is not in the datafile', function () { @@ -225,7 +228,7 @@ describe('lib/core/bucketer', function () { assert.throws(function () { bucketer.bucket(bucketerParamsWithInvalidGroupId); - }, sprintf(ERROR_MESSAGES.INVALID_GROUP_ID, 'BUCKETER', '6969')); + }, sprintf(INVALID_GROUP_ID, 'BUCKETER', '6969')); }); }); @@ -254,7 +257,7 @@ describe('lib/core/bucketer', function () { sinon.assert.calledOnce(createdLogger.log); var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal(sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '0', 'testUser')); + expect(log1).to.equal(sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '0', 'testUser')); }); it('should return decision response with variation null when a user does not fall into an experiment within an overlapping group', function () { @@ -357,8 +360,8 @@ describe('lib/core/bucketer', function () { bucketer._generateBucketValue(null); } ); expect([ - sprintf(ERROR_MESSAGES.INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read property 'length' of null"), // node v14 - sprintf(ERROR_MESSAGES.INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read properties of null (reading \'length\')") // node v16 + sprintf(INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read property 'length' of null"), // node v14 + sprintf(INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read properties of null (reading \'length\')") // node v16 ]).contain(response.message); }); }); diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index c2c6a0235..96d014dcf 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -27,11 +27,14 @@ import { Group, } from '../../shared_types'; -import { - ERROR_MESSAGES, - LOG_LEVEL, - LOG_MESSAGES, -} from '../../utils/enums'; +import { LOG_LEVEL } from '../../utils/enums'; +import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; + +export const USER_NOT_IN_ANY_EXPERIMENT = '%s: User %s is not in any experiment of group %s.'; +export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = '%s: User %s is not in experiment %s of group %s.'; +export const USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP = '%s: User %s is in experiment %s of group %s.'; +export const USER_ASSIGNED_TO_EXPERIMENT_BUCKET = '%s: Assigned bucket %s to user with bucketing ID %s.'; +export const INVALID_VARIATION_ID = '%s: Bucketed into an invalid variation ID. Returning null.'; const HASH_SEED = 1; const MAX_HASH_VALUE = Math.pow(2, 32); @@ -63,7 +66,7 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (groupId) { const group = bucketerParams.groupIdMap[groupId]; if (!group) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_GROUP_ID, MODULE_NAME, groupId)); + throw new Error(sprintf(INVALID_GROUP_ID, MODULE_NAME, groupId)); } if (group.policy === RANDOM_POLICY) { const bucketedExperimentId = bucketUserIntoExperiment( @@ -77,13 +80,13 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (bucketedExperimentId === null) { bucketerParams.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, + USER_NOT_IN_ANY_EXPERIMENT, MODULE_NAME, bucketerParams.userId, groupId, ); decideReasons.push([ - LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, + USER_NOT_IN_ANY_EXPERIMENT, MODULE_NAME, bucketerParams.userId, groupId, @@ -98,14 +101,14 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (bucketedExperimentId !== bucketerParams.experimentId) { bucketerParams.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, ); decideReasons.push([ - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, @@ -120,14 +123,14 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse // Continue bucketing if user is bucketed into specified experiment bucketerParams.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, ); decideReasons.push([ - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, @@ -140,13 +143,13 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse bucketerParams.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, MODULE_NAME, bucketValue, bucketerParams.userId, ); decideReasons.push([ - LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, MODULE_NAME, bucketValue, bucketerParams.userId, @@ -156,8 +159,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (entityId !== null) { if (!bucketerParams.variationIdMap[entityId]) { if (entityId) { - bucketerParams.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.INVALID_VARIATION_ID, MODULE_NAME); - decideReasons.push([LOG_MESSAGES.INVALID_VARIATION_ID, MODULE_NAME]); + bucketerParams.logger.log(LOG_LEVEL.WARNING, INVALID_VARIATION_ID, MODULE_NAME); + decideReasons.push([INVALID_VARIATION_ID, MODULE_NAME]); } return { result: null, @@ -190,7 +193,7 @@ export const bucketUserIntoExperiment = function( const bucketValue = _generateBucketValue(bucketingKey); logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, MODULE_NAME, bucketValue, userId, @@ -235,7 +238,7 @@ export const _generateBucketValue = function(bucketingKey: string): number { const ratio = hashValue / MAX_HASH_VALUE; return Math.floor(ratio * MAX_TRAFFIC_VALUE); } catch (ex: any) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_BUCKETING_ID, MODULE_NAME, bucketingKey, ex.message)); + throw new Error(sprintf(INVALID_BUCKETING_ID, MODULE_NAME, bucketingKey, ex.message)); } }; diff --git a/lib/core/custom_attribute_condition_evaluator/index.tests.js b/lib/core/custom_attribute_condition_evaluator/index.tests.js index b594cf898..5cf0e44c9 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.tests.js +++ b/lib/core/custom_attribute_condition_evaluator/index.tests.js @@ -19,10 +19,17 @@ import { sprintf } from '../../utils/fns'; import { LOG_LEVEL, - LOG_MESSAGES, } from '../../utils/enums'; import * as logging from '../../modules/logging'; import * as customAttributeEvaluator from './'; +import { + MISSING_ATTRIBUTE_VALUE, + OUT_OF_BOUNDS, + UNEXPECTED_CONDITION_VALUE, + UNEXPECTED_TYPE, + UNEXPECTED_TYPE_NULL, +} from '../../log_messages'; +import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; var browserConditionSafari = { name: 'browser_type', @@ -104,7 +111,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { sinon.assert.calledOnce(stubLogHandler.log); assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidMatchCondition))); + assert.strictEqual(logMessage, sprintf(UNKNOWN_MATCH_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidMatchCondition))); }); describe('exists match type', function() { @@ -186,7 +193,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { sinon.assert.calledOnce(stubLogHandler.log); assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidExactCondition))); + assert.strictEqual(logMessage, sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidExactCondition))); }); it('should log and return null if the user-provided value is of a different type than the condition value', function() { @@ -204,7 +211,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) ); }); @@ -219,7 +226,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) + sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) ); }); @@ -231,7 +238,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.MISSING_ATTRIBUTE_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) + sprintf(MISSING_ATTRIBUTE_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) ); }); @@ -249,7 +256,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) ); }); }); @@ -299,11 +306,11 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage2 = stubLogHandler.log.args[1][1]; assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType1, exactNumberCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType1, exactNumberCondition.name) ); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType2, exactNumberCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType2, exactNumberCondition.name) ); }); @@ -325,11 +332,11 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage2 = stubLogHandler.log.args[1][1]; assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) + sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) ); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) + sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) ); }); @@ -365,11 +372,11 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage2 = stubLogHandler.log.args[1][1]; assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition1)) + sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition1)) ); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition2)) + sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition2)) ); }); }); @@ -446,7 +453,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), userValueType, substringCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), userValueType, substringCondition.name) ); }); @@ -465,7 +472,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(nonStringCondition)) + sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(nonStringCondition)) ); }); @@ -477,7 +484,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), substringCondition.name) + sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), substringCondition.name) ); }); @@ -542,11 +549,11 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage2 = stubLogHandler.log.args[1][1]; assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType1, gtCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType1, gtCondition.name) ); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType2, gtCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType2, gtCondition.name) ); }); @@ -571,11 +578,11 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage2 = stubLogHandler.log.args[1][1]; assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) + sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) ); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) + sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) ); }); @@ -587,7 +594,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) + sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) ); }); @@ -619,7 +626,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[2][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) + sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) ); }); }); @@ -679,11 +686,11 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage2 = stubLogHandler.log.args[1][1]; assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType1, ltCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType1, ltCondition.name) ); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType2, ltCondition.name) + sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType2, ltCondition.name) ); }); @@ -712,11 +719,11 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage2 = stubLogHandler.log.args[1][1]; assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) + sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) ); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) + sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) ); }); @@ -728,7 +735,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[0][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) + sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) ); }); @@ -760,7 +767,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var logMessage = stubLogHandler.log.args[2][1]; assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) + sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) ); }); }); diff --git a/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts index a887a2633..ab30a214d 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/lib/core/custom_attribute_condition_evaluator/index.ts @@ -17,8 +17,15 @@ import { getLogger } from '../../modules/logging'; import { Condition, OptimizelyUserContext } from '../../shared_types'; import fns from '../../utils/fns'; -import { LOG_MESSAGES } from '../../utils/enums'; import { compareVersion } from '../../utils/semantic_version'; +import { + MISSING_ATTRIBUTE_VALUE, + OUT_OF_BOUNDS, + UNEXPECTED_CONDITION_VALUE, + UNEXPECTED_TYPE, + UNEXPECTED_TYPE_NULL, +} from '../../log_messages'; +import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; const MODULE_NAME = 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR'; @@ -81,14 +88,14 @@ export function evaluate(condition: Condition, user: OptimizelyUserContext): boo const userAttributes = user.getAttributes(); const conditionMatch = condition.match; if (typeof conditionMatch !== 'undefined' && MATCH_TYPES.indexOf(conditionMatch) === -1) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); + logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); return null; } const attributeKey = condition.name; if (!userAttributes.hasOwnProperty(attributeKey) && conditionMatch != EXISTS_MATCH_TYPE) { logger.debug( - LOG_MESSAGES.MISSING_ATTRIBUTE_VALUE, MODULE_NAME, JSON.stringify(condition), attributeKey + MISSING_ATTRIBUTE_VALUE, MODULE_NAME, JSON.stringify(condition), attributeKey ); return null; } @@ -136,28 +143,28 @@ function exactEvaluator(condition: Condition, user: OptimizelyUserContext): bool (fns.isNumber(conditionValue) && !fns.isSafeInteger(conditionValue)) ) { logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) ); return null; } if (userValue === null) { logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName ); return null; } if (!isValueTypeValidForExactConditions(userValue) || conditionValueType !== userValueType) { logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName ); return null; } if (fns.isNumber(userValue) && !fns.isSafeInteger(userValue)) { logger.warn( - LOG_MESSAGES.OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName + OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName ); return null; } @@ -196,28 +203,28 @@ function validateValuesForNumericCondition(condition: Condition, user: Optimizel if (conditionValue === null || !fns.isSafeInteger(conditionValue)) { logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) ); return false; } if (userValue === null) { logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName ); return false; } if (!fns.isNumber(userValue)) { logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName ); return false; } if (!fns.isSafeInteger(userValue)) { logger.warn( - LOG_MESSAGES.OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName + OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName ); return false; } @@ -325,21 +332,21 @@ function substringEvaluator(condition: Condition, user: OptimizelyUserContext): if (typeof conditionValue !== 'string') { logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) ); return null; } if (userValue === null) { logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName ); return null; } if (typeof userValue !== 'string') { logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName ); return null; } @@ -363,21 +370,21 @@ function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserConte if (typeof conditionValue !== 'string') { logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) ); return null; } if (userValue === null) { logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName ); return null; } if (typeof userValue !== 'string') { logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName ); return null; } diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 5522e3905..7c21034ad 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -22,9 +22,7 @@ import { AUDIENCE_EVALUATION_TYPES, CONTROL_ATTRIBUTES, DECISION_SOURCES, - ERROR_MESSAGES, LOG_LEVEL, - LOG_MESSAGES, } from '../../utils/enums'; import { getAudiencesById, @@ -54,6 +52,49 @@ import { UserProfileService, Variation, } from '../../shared_types'; +import { + IMPROPERLY_FORMATTED_EXPERIMENT, + INVALID_ROLLOUT_ID, + INVALID_USER_ID, + INVALID_VARIATION_KEY, + NO_VARIATION_FOR_EXPERIMENT_KEY, + USER_NOT_IN_FORCED_VARIATION, + USER_PROFILE_LOOKUP_ERROR, + USER_PROFILE_SAVE_ERROR, +} from '../../error_messages'; +import { + AUDIENCE_EVALUATION_RESULT_COMBINED, + BUCKETING_ID_NOT_STRING, + EVALUATING_AUDIENCES_COMBINED, + EXPERIMENT_NOT_RUNNING, + FEATURE_HAS_NO_EXPERIMENTS, + FORCED_BUCKETING_FAILED, + NO_ROLLOUT_EXISTS, + RETURNING_STORED_VARIATION, + ROLLOUT_HAS_NO_EXPERIMENTS, + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND, + USER_BUCKETED_INTO_TARGETING_RULE, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_FORCED_IN_VARIATION, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + USER_HAS_NO_VARIATION, + USER_HAS_VARIATION, + USER_IN_ROLLOUT, + USER_MAPPED_TO_FORCED_VARIATION, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_NOT_IN_EXPERIMENT, + USER_NOT_IN_ROLLOUT, + VALID_BUCKETING_ID, + VARIATION_REMOVED_FOR_USER, +} from '../../log_messages'; export const MODULE_NAME = 'DECISION_SERVICE'; @@ -129,8 +170,8 @@ export class DecisionService { const decideReasons: (string | number)[][] = []; const experimentKey = experiment.key; if (!this.checkIfExperimentIsActive(configObj, experimentKey)) { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey); - decideReasons.push([LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey]); + this.logger.log(LOG_LEVEL.INFO, EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey); + decideReasons.push([EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey]); return { result: null, reasons: decideReasons, @@ -163,14 +204,14 @@ export class DecisionService { if (variation) { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.RETURNING_STORED_VARIATION, + RETURNING_STORED_VARIATION, MODULE_NAME, variation.key, experimentKey, userId, ); decideReasons.push([ - LOG_MESSAGES.RETURNING_STORED_VARIATION, + RETURNING_STORED_VARIATION, MODULE_NAME, variation.key, experimentKey, @@ -195,13 +236,13 @@ export class DecisionService { if (!decisionifUserIsInAudience.result) { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + USER_NOT_IN_EXPERIMENT, MODULE_NAME, userId, experimentKey, ); decideReasons.push([ - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + USER_NOT_IN_EXPERIMENT, MODULE_NAME, userId, experimentKey, @@ -222,13 +263,13 @@ export class DecisionService { if (!variation) { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_VARIATION, + USER_HAS_NO_VARIATION, MODULE_NAME, userId, experimentKey, ); decideReasons.push([ - LOG_MESSAGES.USER_HAS_NO_VARIATION, + USER_HAS_NO_VARIATION, MODULE_NAME, userId, experimentKey, @@ -241,14 +282,14 @@ export class DecisionService { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_VARIATION, + USER_HAS_VARIATION, MODULE_NAME, userId, variation.key, experimentKey, ); decideReasons.push([ - LOG_MESSAGES.USER_HAS_VARIATION, + USER_HAS_VARIATION, MODULE_NAME, userId, variation.key, @@ -342,13 +383,13 @@ export class DecisionService { if (experiment.variationKeyMap.hasOwnProperty(forcedVariationKey)) { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_FORCED_IN_VARIATION, + USER_FORCED_IN_VARIATION, MODULE_NAME, userId, forcedVariationKey, ); decideReasons.push([ - LOG_MESSAGES.USER_FORCED_IN_VARIATION, + USER_FORCED_IN_VARIATION, MODULE_NAME, userId, forcedVariationKey, @@ -360,13 +401,13 @@ export class DecisionService { } else { this.logger.log( LOG_LEVEL.ERROR, - LOG_MESSAGES.FORCED_BUCKETING_FAILED, + FORCED_BUCKETING_FAILED, MODULE_NAME, forcedVariationKey, userId, ); decideReasons.push([ - LOG_MESSAGES.FORCED_BUCKETING_FAILED, + FORCED_BUCKETING_FAILED, MODULE_NAME, forcedVariationKey, userId, @@ -407,14 +448,14 @@ export class DecisionService { const audiencesById = getAudiencesById(configObj); this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.EVALUATING_AUDIENCES_COMBINED, + EVALUATING_AUDIENCES_COMBINED, MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, JSON.stringify(experimentAudienceConditions), ); decideReasons.push([ - LOG_MESSAGES.EVALUATING_AUDIENCES_COMBINED, + EVALUATING_AUDIENCES_COMBINED, MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, @@ -423,14 +464,14 @@ export class DecisionService { const result = this.audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, user); this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, result.toString().toUpperCase(), ); decideReasons.push([ - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, @@ -493,7 +534,7 @@ export class DecisionService { } else { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.SAVED_VARIATION_NOT_FOUND, + SAVED_VARIATION_NOT_FOUND, MODULE_NAME, userId, variationId, experiment.key, @@ -524,7 +565,7 @@ export class DecisionService { } catch (ex: any) { this.logger.log( LOG_LEVEL.ERROR, - ERROR_MESSAGES.USER_PROFILE_LOOKUP_ERROR, + USER_PROFILE_LOOKUP_ERROR, MODULE_NAME, userId, ex.message, @@ -574,12 +615,12 @@ export class DecisionService { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.SAVED_USER_VARIATION, + SAVED_USER_VARIATION, MODULE_NAME, userId, ); } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.USER_PROFILE_SAVE_ERROR, MODULE_NAME, userId, ex.message); + this.logger.log(LOG_LEVEL.ERROR, USER_PROFILE_SAVE_ERROR, MODULE_NAME, userId, ex.message); } } @@ -630,11 +671,11 @@ export class DecisionService { const userId = user.getUserId(); if (rolloutDecision.variation) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key); - decideReasons.push([LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + this.logger.log(LOG_LEVEL.DEBUG, USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key); + decideReasons.push([USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); } else { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key); - decideReasons.push([LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + this.logger.log(LOG_LEVEL.DEBUG, USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key); + decideReasons.push([USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); } decisions.push({ @@ -718,8 +759,8 @@ export class DecisionService { } } } else { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key); - decideReasons.push([LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key]); + this.logger.log(LOG_LEVEL.DEBUG, FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key); + decideReasons.push([FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key]); } variationForFeatureExperiment = { @@ -742,8 +783,8 @@ export class DecisionService { const decideReasons: (string | number)[][] = []; let decisionObj: DecisionObj; if (!feature.rolloutId) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key); - decideReasons.push([LOG_MESSAGES.NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key]); + this.logger.log(LOG_LEVEL.DEBUG, NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key); + decideReasons.push([NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key]); decisionObj = { experiment: null, variation: null, @@ -760,12 +801,12 @@ export class DecisionService { if (!rollout) { this.logger.log( LOG_LEVEL.ERROR, - ERROR_MESSAGES.INVALID_ROLLOUT_ID, + INVALID_ROLLOUT_ID, MODULE_NAME, feature.rolloutId, feature.key, ); - decideReasons.push([ERROR_MESSAGES.INVALID_ROLLOUT_ID, MODULE_NAME, feature.rolloutId, feature.key]); + decideReasons.push([INVALID_ROLLOUT_ID, MODULE_NAME, feature.rolloutId, feature.key]); decisionObj = { experiment: null, variation: null, @@ -781,11 +822,11 @@ export class DecisionService { if (rolloutRules.length === 0) { this.logger.log( LOG_LEVEL.ERROR, - LOG_MESSAGES.ROLLOUT_HAS_NO_EXPERIMENTS, + ROLLOUT_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.rolloutId, ); - decideReasons.push([LOG_MESSAGES.ROLLOUT_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.rolloutId]); + decideReasons.push([ROLLOUT_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.rolloutId]); decisionObj = { experiment: null, variation: null, @@ -851,9 +892,9 @@ export class DecisionService { ) { if (typeof attributes[CONTROL_ATTRIBUTES.BUCKETING_ID] === 'string') { bucketingId = String(attributes[CONTROL_ATTRIBUTES.BUCKETING_ID]); - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.VALID_BUCKETING_ID, MODULE_NAME, bucketingId); + this.logger.log(LOG_LEVEL.DEBUG, VALID_BUCKETING_ID, MODULE_NAME, bucketingId); } else { - this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.BUCKETING_ID_NOT_STRING, MODULE_NAME); + this.logger.log(LOG_LEVEL.WARNING, BUCKETING_ID_NOT_STRING, MODULE_NAME); } } @@ -887,14 +928,14 @@ export class DecisionService { if (ruleKey) { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, flagKey, ruleKey, userId ); decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, flagKey, ruleKey, @@ -903,13 +944,13 @@ export class DecisionService { } else { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, flagKey, userId ); decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, flagKey, userId @@ -919,13 +960,13 @@ export class DecisionService { if (ruleKey) { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, flagKey, ruleKey, userId ); decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, flagKey, ruleKey, userId @@ -933,12 +974,12 @@ export class DecisionService { } else { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, flagKey, userId ); decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, flagKey, userId ]) @@ -961,20 +1002,20 @@ export class DecisionService { */ removeForcedVariation(userId: string, experimentId: string, experimentKey: string): void { if (!userId) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_ID, MODULE_NAME)); + throw new Error(sprintf(INVALID_USER_ID, MODULE_NAME)); } if (this.forcedVariationMap.hasOwnProperty(userId)) { delete this.forcedVariationMap[userId][experimentId]; this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.VARIATION_REMOVED_FOR_USER, + VARIATION_REMOVED_FOR_USER, MODULE_NAME, experimentKey, userId, ); } else { - throw new Error(sprintf(ERROR_MESSAGES.USER_NOT_IN_FORCED_VARIATION, MODULE_NAME, userId)); + throw new Error(sprintf(USER_NOT_IN_FORCED_VARIATION, MODULE_NAME, userId)); } } @@ -995,7 +1036,7 @@ export class DecisionService { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, + USER_MAPPED_TO_FORCED_VARIATION, MODULE_NAME, variationId, experimentId, @@ -1021,7 +1062,7 @@ export class DecisionService { if (!experimentToVariationMap) { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, MODULE_NAME, userId, ); @@ -1041,12 +1082,12 @@ export class DecisionService { // catching improperly formatted experiments this.logger.log( LOG_LEVEL.ERROR, - ERROR_MESSAGES.IMPROPERLY_FORMATTED_EXPERIMENT, + IMPROPERLY_FORMATTED_EXPERIMENT, MODULE_NAME, experimentKey, ); decideReasons.push([ - ERROR_MESSAGES.IMPROPERLY_FORMATTED_EXPERIMENT, + IMPROPERLY_FORMATTED_EXPERIMENT, MODULE_NAME, experimentKey, ]); @@ -1071,7 +1112,7 @@ export class DecisionService { if (!variationId) { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, MODULE_NAME, experimentKey, userId, @@ -1086,14 +1127,14 @@ export class DecisionService { if (variationKey) { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_FORCED_VARIATION, + USER_HAS_FORCED_VARIATION, MODULE_NAME, variationKey, experimentKey, userId, ); decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_VARIATION, + USER_HAS_FORCED_VARIATION, MODULE_NAME, variationKey, experimentKey, @@ -1102,7 +1143,7 @@ export class DecisionService { } else { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, MODULE_NAME, experimentKey, userId, @@ -1130,7 +1171,7 @@ export class DecisionService { variationKey: string | null ): boolean { if (variationKey != null && !stringValidator.validate(variationKey)) { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.INVALID_VARIATION_KEY, MODULE_NAME); + this.logger.log(LOG_LEVEL.ERROR, INVALID_VARIATION_KEY, MODULE_NAME); return false; } @@ -1143,7 +1184,7 @@ export class DecisionService { // catching improperly formatted experiments this.logger.log( LOG_LEVEL.ERROR, - ERROR_MESSAGES.IMPROPERLY_FORMATTED_EXPERIMENT, + IMPROPERLY_FORMATTED_EXPERIMENT, MODULE_NAME, experimentKey, ); @@ -1170,7 +1211,7 @@ export class DecisionService { if (!variationId) { this.logger.log( LOG_LEVEL.ERROR, - ERROR_MESSAGES.NO_VARIATION_FOR_EXPERIMENT_KEY, + NO_VARIATION_FOR_EXPERIMENT_KEY, MODULE_NAME, variationKey, experimentKey, @@ -1263,13 +1304,13 @@ export class DecisionService { if (decisionifUserIsInAudience.result) { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, MODULE_NAME, userId, loggingKey ); decideReasons.push([ - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, MODULE_NAME, userId, loggingKey @@ -1285,13 +1326,13 @@ export class DecisionService { if (bucketedVariation) { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_BUCKETED_INTO_TARGETING_RULE, + USER_BUCKETED_INTO_TARGETING_RULE, MODULE_NAME, userId, loggingKey ); decideReasons.push([ - LOG_MESSAGES.USER_BUCKETED_INTO_TARGETING_RULE, + USER_BUCKETED_INTO_TARGETING_RULE, MODULE_NAME, userId, loggingKey]); @@ -1299,13 +1340,13 @@ export class DecisionService { // skip this logging for EveryoneElse since this has a message not for EveryoneElse this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, MODULE_NAME, userId, loggingKey ); decideReasons.push([ - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, MODULE_NAME, userId, loggingKey @@ -1317,13 +1358,13 @@ export class DecisionService { } else { this.logger.log( LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, MODULE_NAME, userId, loggingKey ); decideReasons.push([ - LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, MODULE_NAME, userId, loggingKey diff --git a/lib/error_messages.ts b/lib/error_messages.ts new file mode 100644 index 000000000..18d85ac13 --- /dev/null +++ b/lib/error_messages.ts @@ -0,0 +1,107 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const BROWSER_ODP_MANAGER_INITIALIZATION_FAILED = '%s: Error initializing Browser ODP Manager.'; +export const CONDITION_EVALUATOR_ERROR = '%s: Error evaluating audience condition of type %s: %s'; +export const DATAFILE_AND_SDK_KEY_MISSING = + '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely'; +export const EXPERIMENT_KEY_NOT_IN_DATAFILE = '%s: Experiment key %s is not in datafile.'; +export const FEATURE_NOT_IN_DATAFILE = '%s: Feature key %s is not in datafile.'; +export const FETCH_SEGMENTS_FAILED_NETWORK_ERROR = '%s: Audience segments fetch failed. (network error)'; +export const FETCH_SEGMENTS_FAILED_DECODE_ERROR = '%s: Audience segments fetch failed. (decode error)'; +export const IMPROPERLY_FORMATTED_EXPERIMENT = '%s: Experiment key %s is improperly formatted.'; +export const INVALID_ATTRIBUTES = '%s: Provided attributes are in an invalid format.'; +export const INVALID_BUCKETING_ID = '%s: Unable to generate hash for bucketing ID %s: %s'; +export const INVALID_DATAFILE = '%s: Datafile is invalid - property %s: %s'; +export const INVALID_DATAFILE_MALFORMED = '%s: Datafile is invalid because it is malformed.'; +export const INVALID_CONFIG = '%s: Provided Optimizely config is in an invalid format.'; +export const INVALID_JSON = '%s: JSON object is not valid.'; +export const INVALID_ERROR_HANDLER = '%s: Provided "errorHandler" is in an invalid format.'; +export const INVALID_EVENT_DISPATCHER = '%s: Provided "eventDispatcher" is in an invalid format.'; +export const INVALID_EVENT_TAGS = '%s: Provided event tags are in an invalid format.'; +export const INVALID_EXPERIMENT_KEY = + '%s: Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; +export const INVALID_EXPERIMENT_ID = '%s: Experiment ID %s is not in datafile.'; +export const INVALID_GROUP_ID = '%s: Group ID %s is not in datafile.'; +export const INVALID_LOGGER = '%s: Provided "logger" is in an invalid format.'; +export const INVALID_ROLLOUT_ID = '%s: Invalid rollout ID %s attached to feature %s'; +export const INVALID_USER_ID = '%s: Provided user ID is in an invalid format.'; +export const INVALID_USER_PROFILE_SERVICE = '%s: Provided user profile service instance is in an invalid format: %s.'; +export const LOCAL_STORAGE_DOES_NOT_EXIST = 'Error accessing window localStorage.'; +export const MISSING_INTEGRATION_KEY = + '%s: Integration key missing from datafile. All integrations should include a key.'; +export const NO_DATAFILE_SPECIFIED = '%s: No datafile specified. Cannot start optimizely.'; +export const NO_JSON_PROVIDED = '%s: No JSON object to validate against schema.'; +export const NO_EVENT_PROCESSOR = 'No event processor is provided'; +export const NO_VARIATION_FOR_EXPERIMENT_KEY = '%s: No variation key %s defined in datafile for experiment %s.'; +export const ODP_CONFIG_NOT_AVAILABLE = '%s: ODP is not integrated to the project.'; +export const ODP_EVENT_FAILED = 'ODP event send failed.'; +export const ODP_EVENT_MANAGER_IS_NOT_RUNNING = 'ODP event manager is not running.'; +export const ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE = 'ODP events should have at least one key-value pair in identifiers.'; +export const ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING = + '%s: ODP unable to fetch qualified segments (Segments Manager not initialized).'; +export const ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING = + '%s: ODP identify event %s is not dispatched (Event Manager not instantiated).'; +export const ODP_INITIALIZATION_FAILED = '%s: ODP failed to initialize.'; +export const ODP_INVALID_DATA = '%s: ODP data is not valid'; +export const ODP_EVENT_FAILED_ODP_MANAGER_MISSING = '%s: ODP Event failed to send. (ODP Manager not initialized).'; +export const ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING = + '%s: ODP failed to Fetch Qualified Segments. (ODP Manager not initialized).'; +export const ODP_IDENTIFY_USER_FAILED_ODP_MANAGER_MISSING = + '%s: ODP failed to Identify User. (ODP Manager not initialized).'; +export const ODP_IDENTIFY_USER_FAILED_USER_CONTEXT_INITIALIZATION = + '%s: ODP failed to Identify User. (Failed during User Context Initialization).'; +export const ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING = + '%s: ODP Manager failed to update OdpConfig settings for internal event manager. (Event Manager not initialized).'; +export const ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING = + '%s: ODP Manager failed to update OdpConfig settings for internal segments manager. (Segments Manager not initialized).'; +export const ODP_NOT_ENABLED = 'ODP is not enabled'; +export const ODP_NOT_INTEGRATED = '%s: ODP is not integrated'; +export const ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING = + '%s: ODP send event %s was not dispatched (Event Manager not instantiated).'; +export const ODP_SEND_EVENT_FAILED_UID_MISSING = + '%s: ODP send event %s was not dispatched (No valid user identifier provided).'; +export const ODP_SEND_EVENT_FAILED_VUID_MISSING = '%s: ODP send event %s was not dispatched (Unable to fetch VUID).'; +export const ODP_VUID_INITIALIZATION_FAILED = '%s: ODP VUID initialization failed.'; +export const ODP_VUID_REGISTRATION_FAILED = '%s: ODP VUID failed to be registered.'; +export const ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING = + '%s: ODP register vuid failed. (Event Manager not instantiated).'; +export const UNDEFINED_ATTRIBUTE = '%s: Provided attribute: %s has an undefined value.'; +export const UNRECOGNIZED_ATTRIBUTE = + '%s: Unrecognized attribute %s provided. Pruning before sending event to Optimizely.'; +export const UNABLE_TO_CAST_VALUE = '%s: Unable to cast value %s to type %s, returning null.'; +export const USER_NOT_IN_FORCED_VARIATION = + '%s: User %s is not in the forced variation map. Cannot remove their forced variation.'; +export const USER_PROFILE_LOOKUP_ERROR = '%s: Error while looking up user profile for user ID "%s": %s.'; +export const USER_PROFILE_SAVE_ERROR = '%s: Error while saving user profile for user ID "%s": %s.'; +export const VARIABLE_KEY_NOT_IN_DATAFILE = + '%s: Variable with key "%s" associated with feature with key "%s" is not in datafile.'; +export const VARIATION_ID_NOT_IN_DATAFILE = '%s: No variation ID %s defined in datafile for experiment %s.'; +export const VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT = '%s: Variation ID %s is not in the datafile.'; +export const INVALID_INPUT_FORMAT = '%s: Provided %s is in an invalid format.'; +export const INVALID_DATAFILE_VERSION = + '%s: This version of the JavaScript SDK does not support the given datafile version: %s'; +export const INVALID_VARIATION_KEY = '%s: Provided variation key is in an invalid format.'; +export const UNABLE_TO_GET_VUID = 'Unable to get VUID - ODP Manager is not instantiated yet.'; +export const ERROR_FETCHING_DATAFILE = 'Error fetching datafile: %s'; +export const DATAFILE_FETCH_REQUEST_FAILED = 'Datafile fetch request failed with status: %s'; +export const EVENT_DATA_FOUND_TO_BE_INVALID = 'Event data found to be invalid.'; +export const EVENT_ACTION_INVALID = 'Event action invalid.'; +export const FAILED_TO_SEND_ODP_EVENTS = 'failed to send odp events'; +export const UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE = 'Unable to get VUID - VuidManager is not available' +export const UNKNOWN_CONDITION_TYPE = + '%s: Audience condition %s has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.'; +export const UNKNOWN_MATCH_TYPE = + '%s: Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.'; diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index f37708521..76e737a9d 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -27,6 +27,8 @@ import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; import { EventEmitter } from "../utils/event_emitter/event_emitter"; import { IdGenerator } from "../utils/id_generator"; import { areEventContextsEqual } from "./event_builder/user_event"; +import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "../exception_messages"; +import { sprintf } from "../utils/fns"; export type EventWithId = { id: string; @@ -160,7 +162,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { const dispatcher = closing && this.closingEventDispatcher ? this.closingEventDispatcher : this.eventDispatcher; return dispatcher.dispatchEvent(request).then((res) => { if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - return Promise.reject(new Error(`Failed to dispatch events: ${res.statusCode}`)); + return Promise.reject(new Error(sprintf(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode))); } return Promise.resolve(res); }); @@ -195,7 +197,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { }).catch((err) => { // if the dispatch fails, the events will still be // in the store for future processing - this.logger?.error('Failed to dispatch events', err); + this.logger?.error(FAILED_TO_DISPATCH_EVENTS, err); }).finally(() => { this.runningTask.delete(taskId); ids.forEach((id) => this.dispatchingEventIds.delete(id)); @@ -253,7 +255,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { if (this.isNew()) { // TOOD: replace message with imported constants - this.startPromise.reject(new Error('Event processor stopped before it could be started')); + this.startPromise.reject(new Error(EVENT_PROCESSOR_STOPPED)); } this.state = ServiceState.Stopping; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.ts b/lib/event_processor/event_dispatcher/default_dispatcher.ts index b8c73833c..21c42bc5e 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from '../../exception_messages'; import { RequestHandler } from '../../utils/http_request_handler/http'; import { EventDispatcher, EventDispatcherResponse, LogEvent } from './event_dispatcher'; @@ -28,7 +29,7 @@ export class DefaultEventDispatcher implements EventDispatcher { ): Promise<EventDispatcherResponse> { // Non-POST requests not supported if (eventObj.httpVerb !== 'POST') { - return Promise.reject(new Error('Only POST requests are supported')); + return Promise.reject(new Error(ONLY_POST_REQUESTS_ARE_SUPPORTED)); } const dataString = JSON.stringify(eventObj.params); diff --git a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts index a2686b316..605bae2ef 100644 --- a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts +++ b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { SEND_BEACON_FAILED } from '../../exception_messages'; import { EventDispatcher, EventDispatcherResponse } from './event_dispatcher'; export type Event = { @@ -41,7 +42,7 @@ export const dispatchEvent = function( if(success) { return Promise.resolve({}); } - return Promise.reject(new Error('sendBeacon failed')); + return Promise.reject(new Error(SEND_BEACON_FAILED)); } const eventDispatcher : EventDispatcher = { diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 18d066366..30e300dc9 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -60,11 +60,11 @@ async function mockRequireNetInfo() { M._load = (uri: string, parent: string) => { if (uri === '@react-native-community/netinfo') { if (isNetInfoAvailable) return {}; - throw new Error('Module not found: @react-native-community/netinfo'); + throw new Error("Module not found: @react-native-community/netinfo"); } if (uri === '@react-native-async-storage/async-storage') { if (isAsyncStorageAvailable) return {}; - throw new Error('Module not found: @react-native-async-storage/async-storage'); + throw new Error("Module not found: @react-native-async-storage/async-storage"); } return M._load_original(uri, parent); @@ -80,6 +80,7 @@ import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../uti import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; import { BatchEventProcessor } from './batch_event_processor'; +import { MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE } from '../utils/import.react_native/@react-native-async-storage/async-storage'; describe('createForwardingEventProcessor', () => { const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); @@ -163,7 +164,7 @@ describe('createBatchEventProcessor', () => { }); expect(() => createBatchEventProcessor({})).toThrowError( - 'Module not found: @react-native-async-storage/async-storage' + MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE ); isAsyncStorageAvailable = true; diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 768c10e87..dbbe7076c 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -23,6 +23,7 @@ import { buildLogEvent } from './event_builder/log_event'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; +import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from '../exception_messages'; class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; private eventEmitter: EventEmitter<{ dispatch: LogEvent }>; @@ -54,7 +55,7 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new Error('Service stopped before it was started')); + this.startPromise.reject(new Error(SERVICE_STOPPED_BEFORE_IT_WAS_STARTED)); } this.state = ServiceState.Terminated; diff --git a/lib/exception_messages.ts b/lib/exception_messages.ts new file mode 100644 index 000000000..f17fa2821 --- /dev/null +++ b/lib/exception_messages.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' +export const FAILED_TO_DISPATCH_EVENTS_WITH_ARG = 'Failed to dispatch events: %s'; +export const EVENT_PROCESSOR_STOPPED = 'Event processor stopped before it could be started'; +export const SERVICE_STOPPED_BEFORE_IT_WAS_STARTED = 'Service stopped before it was started'; +export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; +export const SEND_BEACON_FAILED = 'sendBeacon failed'; +export const CANNOT_START_WITHOUT_ODP_CONFIG = 'cannot start without ODP config'; +export const START_CALLED_WHEN_ODP_IS_NOT_INTEGRATED = 'start() called when ODP is not integrated'; +export const ODP_ACTION_IS_NOT_VALID = 'ODP action is not valid (cannot be empty).'; +export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before running'; +export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; +export const ONREADY_TIMEOUT_EXPIRED = 'onReady timeout expired after %s ms'; +export const INSTANCE_CLOSED = 'Instance closed'; +export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it could be started'; +export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; +export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; +export const FAILED_TO_STOP = 'Failed to stop'; +export const YOU_MUST_PROVIDE_DATAFILE_IN_SSR = 'You must provide datafile in SSR'; +export const YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE = 'You must provide at least one of sdkKey or datafile'; +export const RETRY_CANCELLED = 'Retry cancelled'; +export const REQUEST_TIMEOUT = 'Request timeout'; +export const REQUEST_ERROR = 'Request error'; +export const REQUEST_FAILED = 'Request failed'; +export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; +export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; +export const PROMISE_SHOULD_NOT_HAVE_RESOLVED = 'Promise should not have resolved'; +export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 0a7859353..b7bd5d0df 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import logging, { getLogger } from './modules/logging/logger'; import { assert } from 'chai'; @@ -25,6 +24,7 @@ import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; +import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; class MockLocalStorage { @@ -55,7 +55,7 @@ if (!global.window) { localStorage: new MockLocalStorage(), }; } catch (e) { - console.error('Unable to overwrite global.window.'); + console.error("Unable to overwrite global.window"); } } @@ -154,7 +154,7 @@ describe('javascript-sdk (Browser)', function() { // }); it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error('Invalid config or something')); + configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 7317540db..681c281c7 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -33,7 +33,7 @@ import { createPollingProjectConfigManager } from './project_config/config_manag import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.browser'; import { createVuidManager } from './vuid/vuid_manager_factory.browser'; import { createOdpManager } from './odp/odp_manager_factory.browser'; - +import { ODP_DISABLED, UNABLE_TO_ATTACH_UNLOAD } from './log_messages'; const logger = getLogger(); logHelper.setLogHandler(loggerPlugin.createLogger()); @@ -107,7 +107,7 @@ const createInstance = function(config: Config): Client | null { } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - logger.error(enums.LOG_MESSAGES.UNABLE_TO_ATTACH_UNLOAD, MODULE_NAME, e.message); + logger.error(UNABLE_TO_ATTACH_UNLOAD, MODULE_NAME, e.message); } return optimizely; diff --git a/lib/index.browser.umdtests.js b/lib/index.browser.umdtests.js index f8be89aca..a13f5046b 100644 --- a/lib/index.browser.umdtests.js +++ b/lib/index.browser.umdtests.js @@ -23,6 +23,7 @@ import Optimizely from './optimizely'; import testData from './tests/test_data'; import packageJSON from '../package.json'; import eventDispatcher from './plugins/event_dispatcher/index.browser'; +import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; describe('javascript-sdk', function() { describe('APIs', function() { @@ -92,7 +93,7 @@ describe('javascript-sdk', function() { }); it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error('Invalid config or something')); + configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); assert.doesNotThrow(function() { var optlyInstance = window.optimizelySdk.createInstance({ datafile: {}, diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js index 076934eda..729af3b19 100644 --- a/lib/index.lite.tests.js +++ b/lib/index.lite.tests.js @@ -22,6 +22,7 @@ import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.lite'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; describe('optimizelyFactory', function() { describe('APIs', function() { @@ -52,7 +53,7 @@ describe('optimizelyFactory', function() { }); it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { - configValidator.validate.throws(new Error('Invalid config or something')); + configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 3495b036b..ee4cf1766 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -23,6 +23,7 @@ import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; describe('optimizelyFactory', function() { describe('APIs', function() { @@ -66,7 +67,7 @@ describe('optimizelyFactory', function() { // }); it('should not throw if the provided config is not valid and log an error if no logger is provided', function() { - configValidator.validate.throws(new Error('Invalid config or something')); + configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), diff --git a/lib/index.node.ts b/lib/index.node.ts index 16605d246..156b06adf 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -28,6 +28,7 @@ import { createPollingProjectConfigManager } from './project_config/config_manag import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; import { createVuidManager } from './vuid/vuid_manager_factory.node'; import { createOdpManager } from './odp/odp_manager_factory.node'; +import { ODP_DISABLED } from './log_messages'; const logger = getLogger(); setLogLevel(LogLevel.ERROR); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 8cedf06d5..565ad0605 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -31,6 +31,7 @@ import { createVuidManager } from './vuid/vuid_manager_factory.react_native'; import 'fast-text-encoding'; import 'react-native-get-random-values'; +import { ODP_DISABLED } from './log_messages'; const logger = getLogger(); setLogHandler(loggerPlugin.createLogger()); diff --git a/lib/log_messages.ts b/lib/log_messages.ts new file mode 100644 index 000000000..4c2ab6e40 --- /dev/null +++ b/lib/log_messages.ts @@ -0,0 +1,122 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const ACTIVATE_USER = '%s: Activating user %s in experiment %s.'; +export const DISPATCH_CONVERSION_EVENT = '%s: Dispatching conversion event to URL %s with params %s.'; +export const DISPATCH_IMPRESSION_EVENT = '%s: Dispatching impression event to URL %s with params %s.'; +export const DEPRECATED_EVENT_VALUE = '%s: Event value is deprecated in %s call.'; +export const EVENT_KEY_NOT_FOUND = '%s: Event key %s is not in datafile.'; +export const EXPERIMENT_NOT_RUNNING = '%s: Experiment %s is not running.'; +export const FEATURE_ENABLED_FOR_USER = '%s: Feature %s is enabled for user %s.'; +export const FEATURE_NOT_ENABLED_FOR_USER = '%s: Feature %s is not enabled for user %s.'; +export const FEATURE_HAS_NO_EXPERIMENTS = '%s: Feature %s is not attached to any experiments.'; +export const FAILED_TO_PARSE_VALUE = '%s: Failed to parse event value "%s" from event tags.'; +export const FAILED_TO_PARSE_REVENUE = '%s: Failed to parse revenue value "%s" from event tags.'; +export const FORCED_BUCKETING_FAILED = '%s: Variation key %s is not in datafile. Not activating user %s.'; +export const INVALID_OBJECT = '%s: Optimizely object is not valid. Failing %s.'; +export const INVALID_CLIENT_ENGINE = '%s: Invalid client engine passed: %s. Defaulting to node-sdk.'; +export const INVALID_DEFAULT_DECIDE_OPTIONS = '%s: Provided default decide options is not an array.'; +export const INVALID_DECIDE_OPTIONS = '%s: Provided decide options is not an array. Using default decide options.'; +export const NOTIFICATION_LISTENER_EXCEPTION = '%s: Notification listener for (%s) threw exception: %s'; +export const NO_ROLLOUT_EXISTS = '%s: There is no rollout of feature %s.'; +export const NOT_ACTIVATING_USER = '%s: Not activating user %s for experiment %s.'; +export const NOT_TRACKING_USER = '% s: Not tracking user %s.'; +export const ODP_DISABLED = 'ODP Disabled.'; +export const ODP_IDENTIFY_FAILED_ODP_DISABLED = '%s: ODP identify event for user %s is not dispatched (ODP disabled).'; +export const ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED = + '%s: ODP identify event %s is not dispatched (ODP not integrated).'; +export const ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED = + '%s: sendOdpEvent failed to parse through and convert fs_user_id aliases'; +export const PARSED_REVENUE_VALUE = '%s: Parsed revenue value "%s" from event tags.'; +export const PARSED_NUMERIC_VALUE = '%s: Parsed event value "%s" from event tags.'; +export const RETURNING_STORED_VARIATION = + '%s: Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; +export const ROLLOUT_HAS_NO_EXPERIMENTS = '%s: Rollout of feature %s has no experiments'; +export const SAVED_USER_VARIATION = '%s: Saved user profile for user "%s".'; +export const UPDATED_USER_VARIATION = '%s: Updated variation "%s" of experiment "%s" for user "%s".'; +export const SAVED_VARIATION_NOT_FOUND = + '%s: User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.'; +export const SHOULD_NOT_DISPATCH_ACTIVATE = '%s: Experiment %s is not in "Running" state. Not activating user.'; +export const SKIPPING_JSON_VALIDATION = '%s: Skipping JSON schema validation.'; +export const TRACK_EVENT = '%s: Tracking event %s for user %s.'; +export const UNRECOGNIZED_DECIDE_OPTION = '%s: Unrecognized decide option %s provided.'; +export const USER_BUCKETED_INTO_TARGETING_RULE = '%s: User %s bucketed into targeting rule %s.'; +export const USER_IN_FEATURE_EXPERIMENT = '%s: User %s is in variation %s of experiment %s on the feature %s.'; +export const USER_IN_ROLLOUT = '%s: User %s is in rollout of feature %s.'; +export const USER_NOT_BUCKETED_INTO_EVERYONE_TARGETING_RULE = + '%s: User %s not bucketed into everyone targeting rule due to traffic allocation.'; +export const USER_NOT_BUCKETED_INTO_ANY_EXPERIMENT_IN_GROUP = '%s: User %s is not in any experiment of group %s.'; +export const USER_NOT_BUCKETED_INTO_TARGETING_RULE = + '%s User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; +export const USER_FORCED_IN_VARIATION = '%s: User %s is forced in variation %s.'; +export const USER_MAPPED_TO_FORCED_VARIATION = + '%s: Set variation %s for experiment %s and user %s in the forced variation map.'; +export const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = + '%s: User %s does not meet conditions for targeting rule %s.'; +export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = '%s: User %s meets conditions for targeting rule %s.'; +export const USER_HAS_VARIATION = '%s: User %s is in variation %s of experiment %s.'; +export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = + 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = + 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = + 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = + 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_VARIATION = + '%s: Variation %s is mapped to experiment %s and user %s in the forced variation map.'; +export const USER_HAS_NO_VARIATION = '%s: User %s is in no variation of experiment %s.'; +export const USER_HAS_NO_FORCED_VARIATION = '%s: User %s is not in the forced variation map.'; +export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = + '%s: No experiment %s mapped to user %s in the forced variation map.'; +export const USER_NOT_IN_EXPERIMENT = '%s: User %s does not meet conditions to be in experiment %s.'; +export const USER_NOT_IN_ROLLOUT = '%s: User %s is not in rollout of feature %s.'; +export const USER_RECEIVED_DEFAULT_VARIABLE_VALUE = + '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".'; +export const FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE = + '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".'; +export const VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE = + '%s: Variable "%s" is not used in variation "%s". Returning default value.'; +export const USER_RECEIVED_VARIABLE_VALUE = '%s: Got variable value "%s" for variable "%s" of feature flag "%s"'; +export const VALID_DATAFILE = '%s: Datafile is valid.'; +export const VALID_USER_PROFILE_SERVICE = '%s: Valid user profile service provided.'; +export const VARIATION_REMOVED_FOR_USER = '%s: Variation mapped to experiment %s has been removed for user %s.'; +export const VARIABLE_REQUESTED_WITH_WRONG_TYPE = + '%s: Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.'; +export const VALID_BUCKETING_ID = '%s: BucketingId is valid: "%s"'; +export const BUCKETING_ID_NOT_STRING = '%s: BucketingID attribute is not a string. Defaulted to userId'; +export const EVALUATING_AUDIENCE = '%s: Starting to evaluate audience "%s" with conditions: %s.'; +export const EVALUATING_AUDIENCES_COMBINED = '%s: Evaluating audiences for %s "%s": %s.'; +export const AUDIENCE_EVALUATION_RESULT = '%s: Audience "%s" evaluated to %s.'; +export const AUDIENCE_EVALUATION_RESULT_COMBINED = '%s: Audiences for %s %s collectively evaluated to %s.'; +export const MISSING_ATTRIBUTE_VALUE = + '%s: Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".'; +export const UNEXPECTED_CONDITION_VALUE = + '%s: Audience condition %s evaluated to UNKNOWN because the condition value is not supported.'; +export const UNEXPECTED_TYPE = + '%s: Audience condition %s evaluated to UNKNOWN because a value of type "%s" was passed for user attribute "%s".'; +export const UNEXPECTED_TYPE_NULL = + '%s: Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".'; +export const UPDATED_OPTIMIZELY_CONFIG = '%s: Updated Optimizely config to revision %s (project id %s)'; +export const OUT_OF_BOUNDS = + '%s: Audience condition %s evaluated to UNKNOWN because the number value for user attribute "%s" is not in the range [-2^53, +2^53].'; +export const UNABLE_TO_ATTACH_UNLOAD = '%s: unable to bind optimizely.close() to page unload event: "%s"'; +export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; +export const ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN = 'Adding Authorization header with Bearer Token'; +export const MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS = 'Making datafile request to url %s with headers: %s'; +export const RESPONSE_STATUS_CODE = 'Response status code: %s'; +export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; + diff --git a/lib/notification_center/index.ts b/lib/notification_center/index.ts index d33c3fa2e..4df708096 100644 --- a/lib/notification_center/index.ts +++ b/lib/notification_center/index.ts @@ -18,13 +18,13 @@ import { objectValues } from '../utils/fns'; import { LOG_LEVEL, - LOG_MESSAGES, } from '../utils/enums'; import { NOTIFICATION_TYPES } from './type'; import { NotificationType, NotificationPayload } from './type'; import { Consumer, Fn } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; +import { NOTIFICATION_LISTENER_EXCEPTION } from '../log_messages'; const MODULE_NAME = 'NOTIFICATION_CENTER'; @@ -111,7 +111,7 @@ export class DefaultNotificationCenter implements NotificationCenter, Notificati } catch (ex: any) { this.logger.log( LOG_LEVEL.ERROR, - LOG_MESSAGES.NOTIFICATION_LISTENER_EXCEPTION, + NOTIFICATION_LISTENER_EXCEPTION, MODULE_NAME, notificationType, ex.message, diff --git a/lib/odp/event_manager/odp_event_api_manager.spec.ts b/lib/odp/event_manager/odp_event_api_manager.spec.ts index 8f6a07fd2..55ec009e1 100644 --- a/lib/odp/event_manager/odp_event_api_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_api_manager.spec.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { describe, it, expect, vi } from 'vitest'; import { DefaultOdpEventApiManager, eventApiRequestGenerator, pixelApiRequestGenerator } from './odp_event_api_manager'; @@ -42,6 +41,7 @@ const PIXEL_URL = 'https://odp.pixel.com/'; const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; +import { REQUEST_FAILED } from '../../exception_messages'; describe('DefaultOdpEventApiManager', () => { it('should generate the event request using the correct odp config and event', async () => { @@ -101,7 +101,7 @@ describe('DefaultOdpEventApiManager', () => { it('should return a promise that fails if the requestHandler response promise fails', async () => { const mockRequestHandler = getMockRequestHandler(); mockRequestHandler.makeRequest.mockReturnValue({ - responsePromise: Promise.reject(new Error('Request failed')), + responsePromise: Promise.reject(new Error(REQUEST_FAILED)), }); const requestGenerator = vi.fn().mockReturnValue({ method: 'PATCH', diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index 12d061918..67b874509 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -23,6 +23,7 @@ import { OdpEvent } from './odp_event'; import { OdpConfig } from '../odp_config'; import { EventDispatchResponse } from './odp_event_api_manager'; import { advanceTimersByTime } from '../../tests/testUtils'; +import { FAILED_TO_DISPATCH_EVENTS } from '../../exception_messages'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; @@ -604,7 +605,7 @@ describe('DefaultOdpEventManager', () => { const repeater = getMockRepeater(); const apiManager = getMockApiManager(); - apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('Failed to dispatch events'))); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error(FAILED_TO_DISPATCH_EVENTS))); const backoffController = { backoff: vi.fn().mockReturnValue(666), @@ -706,7 +707,7 @@ describe('DefaultOdpEventManager', () => { const repeater = getMockRepeater(); const apiManager = getMockApiManager(); - apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('Failed to dispatch events'))); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error(FAILED_TO_DISPATCH_EVENTS))); const backoffController = { backoff: vi.fn().mockReturnValue(666), diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 9db9086a4..6ebe5aaa0 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -22,8 +22,17 @@ import { BackoffController, Repeater } from '../../utils/repeater/repeater'; import { Producer } from '../../utils/type'; import { runWithRetry } from '../../utils/executor/backoff_retry_runner'; import { isSuccessStatusCode } from '../../utils/http_request_handler/http_util'; -import { ERROR_MESSAGES } from '../../utils/enums'; import { ODP_DEFAULT_EVENT_TYPE, ODP_USER_KEY } from '../constant'; +import { + EVENT_ACTION_INVALID, + EVENT_DATA_FOUND_TO_BE_INVALID, + FAILED_TO_SEND_ODP_EVENTS, + ODP_EVENT_MANAGER_IS_NOT_RUNNING, + ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE, + ODP_NOT_INTEGRATED, +} from '../../error_messages'; +import { sprintf } from '../../utils/fns'; +import { FAILED_TO_DISPATCH_EVENTS_WITH_ARG, ODP_EVENT_MANAGER_STOPPED } from '../../exception_messages'; export interface OdpEventManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; @@ -66,8 +75,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag private async executeDispatch(odpConfig: OdpConfig, batch: OdpEvent[]): Promise<unknown> { const res = await this.apiManager.sendEvents(odpConfig, batch); if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - // TODO: replace message with imported constants - return Promise.reject(new Error(`Failed to dispatch events: ${res.statusCode}`)); + return Promise.reject(new Error(sprintf(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode))); } return await Promise.resolve(res); } @@ -89,8 +97,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag return runWithRetry( () => this.executeDispatch(odpConfig, batch), this.retryConfig.backoffProvider(), this.retryConfig.maxRetries ).result.catch((err) => { - // TODO: replace with imported constants - this.logger?.error('failed to send odp events', err); + this.logger?.error(FAILED_TO_SEND_ODP_EVENTS, err); }); } @@ -139,7 +146,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } if (this.isNew()) { - this.startPromise.reject(new Error('odp event manager stopped before it could start')); + this.startPromise.reject(new Error(ODP_EVENT_MANAGER_STOPPED)); } this.flush(); @@ -149,27 +156,27 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag sendEvent(event: OdpEvent): void { if (!this.isRunning()) { - this.logger?.error('ODP event manager is not running.'); + this.logger?.error(ODP_EVENT_MANAGER_IS_NOT_RUNNING); return; } if (!this.odpIntegrationConfig?.integrated) { - this.logger?.error(ERROR_MESSAGES.ODP_NOT_INTEGRATED); + this.logger?.error(ODP_NOT_INTEGRATED); return; } if (event.identifiers.size === 0) { - this.logger?.error('ODP events should have at least one key-value pair in identifiers.'); + this.logger?.error(ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE); return; } if (!this.isDataValid(event.data)) { - this.logger?.error('Event data found to be invalid.'); + this.logger?.error(EVENT_DATA_FOUND_TO_BE_INVALID); return; } if (!event.action ) { - this.logger?.error('Event action invalid.'); + this.logger?.error(EVENT_ACTION_INVALID); return; } diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index 2464bc28b..dc6e2b96b 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -25,6 +25,7 @@ import { ODP_USER_KEY } from './constant'; import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; import { OdpEventManager } from './event_manager/odp_event_manager'; import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; +import { FAILED_TO_STOP } from '../exception_messages'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -166,7 +167,7 @@ describe('DefaultOdpManager', () => { await exhaustMicrotasks(); expect(odpManager.getState()).toEqual(ServiceState.Starting); - eventManagerPromise.reject(new Error('Failed to start')); + eventManagerPromise.reject(new Error("Failed to start")); await expect(odpManager.onRunning()).rejects.toThrow(); await expect(odpManager.onTerminated()).rejects.toThrow(); @@ -186,7 +187,7 @@ describe('DefaultOdpManager', () => { odpManager.start(); expect(odpManager.getState()).toEqual(ServiceState.Starting); - eventManagerPromise.reject(new Error('Failed to start')); + eventManagerPromise.reject(new Error("Failed to start")); await expect(odpManager.onRunning()).rejects.toThrow(); await expect(odpManager.onTerminated()).rejects.toThrow(); @@ -692,7 +693,7 @@ describe('DefaultOdpManager', () => { await exhaustMicrotasks(); expect(odpManager.getState()).toEqual(ServiceState.Stopping); - eventManagerTerminatedPromise.reject(new Error('Failed to stop')); + eventManagerTerminatedPromise.reject(new Error(FAILED_TO_STOP)); await expect(odpManager.onTerminated()).rejects.toThrow(); }); }); diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 560e445a4..05c476ff3 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -29,6 +29,7 @@ import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; import { ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION, ODP_USER_KEY } from './constant'; import { isVuid } from '../vuid/vuid'; import { Maybe } from '../utils/type'; +import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from '../exception_messages'; export interface OdpManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean; @@ -131,7 +132,7 @@ export class DefaultOdpManager extends BaseService implements OdpManager { } if (!this.isRunning()) { - this.startPromise.reject(new Error('odp manager stopped before running')); + this.startPromise.reject(new Error(ODP_MANAGER_STOPPED_BEFORE_RUNNING)); } this.state = ServiceState.Stopping; diff --git a/lib/odp/segment_manager/odp_segment_api_manager.spec.ts b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts index 52237add9..ad07894bd 100644 --- a/lib/odp/segment_manager/odp_segment_api_manager.spec.ts +++ b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts @@ -46,7 +46,7 @@ describe('DefaultOdpSegmentApiManager', () => { const requestHandler = getMockRequestHandler(); requestHandler.makeRequest.mockReturnValue({ abort: () => {}, - responsePromise: Promise.reject(new Error('Request timed out')), + responsePromise: Promise.reject(new Error("Request timed out")), }); const logger = getMockLogger(); const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); diff --git a/lib/odp/segment_manager/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts index dbf83a12f..1dc2eca42 100644 --- a/lib/odp/segment_manager/odp_segment_manager.ts +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { ERROR_MESSAGES } from '../../utils/enums'; import { Cache } from '../../utils/cache/cache'; import { OdpSegmentApiManager } from './odp_segment_api_manager'; import { OdpIntegrationConfig } from '../odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { ODP_USER_KEY } from '../constant'; import { LoggerFacade } from '../../modules/logging'; +import { ODP_CONFIG_NOT_AVAILABLE, ODP_NOT_INTEGRATED } from '../../error_messages'; export interface OdpSegmentManager { fetchQualifiedSegments( @@ -61,12 +61,12 @@ export class DefaultOdpSegmentManager implements OdpSegmentManager { options?: Array<OptimizelySegmentOption> ): Promise<string[] | null> { if (!this.odpIntegrationConfig) { - this.logger?.warn(ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + this.logger?.warn(ODP_CONFIG_NOT_AVAILABLE); return null; } if (!this.odpIntegrationConfig.integrated) { - this.logger?.warn(ERROR_MESSAGES.ODP_NOT_INTEGRATED); + this.logger?.warn(ODP_NOT_INTEGRATED); return null; } diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 364c05658..5ced36a08 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { describe, it, expect, vi } from 'vitest'; import Optimizely from '.'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 187764625..4f121df29 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -18,7 +18,6 @@ import sinon from 'sinon'; import { sprintf } from '../utils/fns'; import { NOTIFICATION_TYPES } from '../notification_center/type'; import * as logging from '../modules/logging'; - import Optimizely from './'; import OptimizelyUserContext from '../optimizely_user_context'; import { OptimizelyDecideOption } from '../shared_types'; @@ -38,10 +37,44 @@ import { createNotificationCenter } from '../notification_center'; import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; +import { + AUDIENCE_EVALUATION_RESULT_COMBINED, + EVENT_KEY_NOT_FOUND, + EXPERIMENT_NOT_RUNNING, + FEATURE_HAS_NO_EXPERIMENTS, + FORCED_BUCKETING_FAILED, + INVALID_CLIENT_ENGINE, + INVALID_DEFAULT_DECIDE_OPTIONS, + INVALID_OBJECT, + NOT_ACTIVATING_USER, + NOT_TRACKING_USER, + RETURNING_STORED_VARIATION, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_FORCED_IN_VARIATION, + USER_HAS_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + USER_HAS_NO_VARIATION, + USER_HAS_VARIATION, + USER_IN_ROLLOUT, + USER_MAPPED_TO_FORCED_VARIATION, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_NOT_IN_EXPERIMENT, + VARIATION_REMOVED_FOR_USER, +} from '../log_messages'; +import { + EXPERIMENT_KEY_NOT_IN_DATAFILE, + INVALID_ATTRIBUTES, + INVALID_EXPERIMENT_KEY, + INVALID_INPUT_FORMAT, + NO_VARIATION_FOR_EXPERIMENT_KEY, + USER_NOT_IN_FORCED_VARIATION, +} from '../error_messages'; +import { FAILED_TO_STOP, ONREADY_TIMEOUT_EXPIRED, PROMISE_SHOULD_NOT_HAVE_RESOLVED } from '../exception_messages'; +import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP } from '../core/bucketer'; -var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; -var LOG_MESSAGES = enums.LOG_MESSAGES; var DECISION_SOURCES = enums.DECISION_SOURCES; var DECISION_MESSAGES = enums.DECISION_MESSAGES; var FEATURE_VARIABLE_TYPES = enums.FEATURE_VARIABLE_TYPES; @@ -158,7 +191,7 @@ describe('lib/optimizely', function() { sinon.assert.called(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_CLIENT_ENGINE, 'OPTIMIZELY', 'undefined')); + assert.strictEqual(logMessage, sprintf(INVALID_CLIENT_ENGINE, 'OPTIMIZELY', 'undefined')); }); it('should log if the defaultDecideOptions passed in are invalid', function() { @@ -175,7 +208,7 @@ describe('lib/optimizely', function() { sinon.assert.called(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_DEFAULT_DECIDE_OPTIONS, 'OPTIMIZELY')); + assert.strictEqual(logMessage, sprintf(INVALID_DEFAULT_DECIDE_OPTIONS, 'OPTIMIZELY')); }); it('should allow passing `react-sdk` as the clientEngine', function() { @@ -759,7 +792,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' ); @@ -767,7 +800,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, + NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperiment' @@ -780,7 +813,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' ); @@ -788,7 +821,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'testExperimentWithAudiences' @@ -797,7 +830,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, + NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences' @@ -810,7 +843,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' ); @@ -818,7 +851,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'groupExperiment1' @@ -827,7 +860,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, + NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'groupExperiment1' @@ -841,12 +874,12 @@ describe('lib/optimizely', function() { var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') + sprintf(EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') ); var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentNotRunning') + sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentNotRunning') ); }); @@ -857,16 +890,16 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); sinon.assert.calledTwice(createdLogger.log); var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage1, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(logMessage1, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'null', 'testExperiment') + sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'null', 'testExperiment') ); }); @@ -879,12 +912,12 @@ describe('lib/optimizely', function() { var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage1, - sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') + sprintf(INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') ); var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'invalidExperimentKey') + sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'invalidExperimentKey') ); }); @@ -894,15 +927,15 @@ describe('lib/optimizely', function() { sinon.assert.notCalled(eventDispatcher.dispatchEvent); sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); sinon.assert.calledTwice(createdLogger.log); var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage1, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(logMessage1, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences') + sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences') ); }); @@ -955,12 +988,12 @@ describe('lib/optimizely', function() { var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage0, - sprintf(LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') + sprintf(USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') ); var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') + sprintf(USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') ); var expectedObj = { @@ -1020,7 +1053,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'activate')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'activate')); sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); @@ -1640,11 +1673,11 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should log a warning for an event key that is not in the datafile and a warning for not tracking user', function() { @@ -1656,7 +1689,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( logCall1, LOG_LEVEL.WARNING, - LOG_MESSAGES.EVENT_KEY_NOT_FOUND, + EVENT_KEY_NOT_FOUND, 'OPTIMIZELY', 'invalidEventKey' ); @@ -1665,7 +1698,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( logCall2, LOG_LEVEL.WARNING, - LOG_MESSAGES.NOT_TRACKING_USER, + NOT_TRACKING_USER, 'OPTIMIZELY', 'testUser' ); @@ -1680,11 +1713,11 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); it('should not throw an error for an event key without associated experiment IDs', function() { @@ -1732,7 +1765,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'track')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'track')); sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); @@ -1755,7 +1788,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' ); @@ -1790,7 +1823,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' ); @@ -1798,7 +1831,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, + USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'testExperimentWithAudiences' @@ -1807,7 +1840,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly( createdLogger.log, LOG_LEVEL.INFO, - LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, + EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning' ); @@ -1820,11 +1853,11 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should log an error for invalid experiment key', function() { @@ -1835,7 +1868,7 @@ describe('lib/optimizely', function() { var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage, - sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') + sprintf(INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') ); }); @@ -1846,11 +1879,11 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); describe('whitelisting', function() { @@ -1873,12 +1906,12 @@ describe('lib/optimizely', function() { var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage0, - sprintf(LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') + sprintf(USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') ); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') + sprintf(USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') ); }); }); @@ -1899,7 +1932,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getVariation')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getVariation')); sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); @@ -1951,7 +1984,7 @@ describe('lib/optimizely', function() { assert.strictEqual(forcedVariation, null); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1')); + assert.strictEqual(logMessage, sprintf(USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1')); }); it('should return null with a null experimentKey', function() { @@ -1959,7 +1992,7 @@ describe('lib/optimizely', function() { assert.strictEqual(forcedVariation, null); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); }); it('should return null with an undefined experimentKey', function() { @@ -1967,7 +2000,7 @@ describe('lib/optimizely', function() { assert.strictEqual(forcedVariation, null); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); }); it('should return null with a null userId', function() { @@ -1975,7 +2008,7 @@ describe('lib/optimizely', function() { assert.strictEqual(forcedVariation, null); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should return null with an undefined userId', function() { @@ -1983,7 +2016,7 @@ describe('lib/optimizely', function() { assert.strictEqual(forcedVariation, null); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); }); @@ -1995,7 +2028,7 @@ describe('lib/optimizely', function() { var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') + sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') ); }); @@ -2034,17 +2067,17 @@ describe('lib/optimizely', function() { assert.strictEqual( setVariationLogMessage, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') + sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') ); assert.strictEqual( variationIsMappedLogMessage, - sprintf(LOG_MESSAGES.USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', 'control', 'testExperiment', 'user1') + sprintf(USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', 'control', 'testExperiment', 'user1') ); assert.strictEqual( variationMappingRemovedLogMessage, - sprintf(LOG_MESSAGES.VARIATION_REMOVED_FOR_USER, 'DECISION_SERVICE', 'testExperiment', 'user1') + sprintf(VARIATION_REMOVED_FOR_USER, 'DECISION_SERVICE', 'testExperiment', 'user1') ); }); @@ -2074,7 +2107,7 @@ describe('lib/optimizely', function() { assert.strictEqual( logMessage, sprintf( - ERROR_MESSAGES.NO_VARIATION_FOR_EXPERIMENT_KEY, + NO_VARIATION_FOR_EXPERIMENT_KEY, 'DECISION_SERVICE', 'definitely_not_valid_variation_key', 'testExperiment' @@ -2089,7 +2122,7 @@ describe('lib/optimizely', function() { var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage, - sprintf(ERROR_MESSAGES.EXPERIMENT_KEY_NOT_IN_DATAFILE, 'PROJECT_CONFIG', 'definitely_not_valid_exp_key') + sprintf(EXPERIMENT_KEY_NOT_IN_DATAFILE, 'PROJECT_CONFIG', 'definitely_not_valid_exp_key') ); }); @@ -2103,14 +2136,14 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') + sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') ); var noVariationToGetLogMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( noVariationToGetLogMessage, sprintf( - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, 'DECISION_SERVICE', 'testExperimentLaunched', 'user1' @@ -2125,7 +2158,7 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') ); }); @@ -2136,7 +2169,7 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') ); }); @@ -2147,7 +2180,7 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') ); }); @@ -2158,7 +2191,7 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') + sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') ); }); @@ -2169,7 +2202,7 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') + sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') ); }); @@ -2185,7 +2218,7 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(ERROR_MESSAGES.USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') + sprintf(USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') ); }); @@ -2196,7 +2229,7 @@ describe('lib/optimizely', function() { var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( setVariationLogMessage, - sprintf(ERROR_MESSAGES.USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') + sprintf(USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') ); }); @@ -2214,13 +2247,13 @@ describe('lib/optimizely', function() { var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); assert.strictEqual( logMessage0, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 133338, 133337, 'user1') + sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 133338, 133337, 'user1') ); var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[1]); assert.strictEqual( logMessage1, - sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') + sprintf(EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') ); }); }); @@ -2245,11 +2278,11 @@ describe('lib/optimizely', function() { sinon.assert.calledThrice(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); sinon.assert.calledThrice(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should return false and throw an error if attributes are invalid', function() { @@ -2258,11 +2291,11 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); }); @@ -4589,20 +4622,20 @@ describe('lib/optimizely', function() { assert.isNull(optlyInstance.createUserContext(1)); sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should call the error handler for invalid attributes and return null', function() { assert.isNull(optlyInstance.createUserContext('user1', 'invalid_attributes')); sinon.assert.calledOnce(errorHandler.handleError); var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); }); @@ -5192,7 +5225,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'exp_with_audience') + sprintf(EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'exp_with_audience') ); }); @@ -5237,7 +5270,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstanceWithUserProfile.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.RETURNING_STORED_VARIATION, 'DECISION_SERVICE', variationKey2, experimentKey, userId) + sprintf(RETURNING_STORED_VARIATION, 'DECISION_SERVICE', variationKey2, experimentKey, userId) ); }); @@ -5253,7 +5286,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', userId, variationKey) + sprintf(USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', userId, variationKey) ); }); @@ -5271,7 +5304,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', variationKey, experimentKey, userId) + sprintf(USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', variationKey, experimentKey, userId) ); }); @@ -5287,7 +5320,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.FORCED_BUCKETING_FAILED, 'DECISION_SERVICE', variationKey, userId) + sprintf(FORCED_BUCKETING_FAILED, 'DECISION_SERVICE', variationKey, userId) ); }); @@ -5300,7 +5333,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') ); }); @@ -5313,7 +5346,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'CA'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') + sprintf(USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') ); }); @@ -5326,7 +5359,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_IN_ROLLOUT, 'DECISION_SERVICE', userId, flagKey) + sprintf(USER_IN_ROLLOUT, 'DECISION_SERVICE', userId, flagKey) ); }); @@ -5339,7 +5372,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'KO'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, 'Everyone Else') + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, 'Everyone Else') ); }); @@ -5352,7 +5385,7 @@ describe('lib/optimizely', function() { user.setAttribute('browser', 'safari'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, 'DECISION_SERVICE', userId, '2') + sprintf(USER_NOT_BUCKETED_INTO_TARGETING_RULE, 'DECISION_SERVICE', userId, '2') ); }); @@ -5366,7 +5399,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_HAS_VARIATION, 'DECISION_SERVICE', userId, variationKey, experimentKey) + sprintf(USER_HAS_VARIATION, 'DECISION_SERVICE', userId, variationKey, experimentKey) ); }); @@ -5384,7 +5417,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_HAS_NO_VARIATION, 'DECISION_SERVICE', userId, experimentKey) + sprintf(USER_HAS_NO_VARIATION, 'DECISION_SERVICE', userId, experimentKey) ); }); @@ -5402,7 +5435,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'BUCKETER', userId, experimentKey, groupId) + sprintf(USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'BUCKETER', userId, experimentKey, groupId) ); }); @@ -5417,7 +5450,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, 'DECISION_SERVICE', flagKey) + sprintf(FEATURE_HAS_NO_EXPERIMENTS, 'DECISION_SERVICE', flagKey) ); }); @@ -5430,7 +5463,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', userId, experimentKey) + sprintf(USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', userId, experimentKey) ); }); @@ -5449,7 +5482,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -5474,7 +5507,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -5499,7 +5532,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -5524,7 +5557,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -5549,7 +5582,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -5574,7 +5607,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -5599,7 +5632,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -5623,7 +5656,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, 'DECISION_SERVICE', 'experiment', experimentKey, @@ -8988,7 +9021,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariable')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariable')); }); it('returns null from getFeatureVariableBoolean when optimizely object is not a valid instance', function() { @@ -9007,7 +9040,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableBoolean')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableBoolean')); }); it('returns null from getFeatureVariableDouble when optimizely object is not a valid instance', function() { @@ -9026,7 +9059,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableDouble')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableDouble')); }); it('returns null from getFeatureVariableInteger when optimizely object is not a valid instance', function() { @@ -9045,7 +9078,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableInteger')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableInteger')); }); it('returns null from getFeatureVariableString when optimizely object is not a valid instance', function() { @@ -9064,7 +9097,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableString')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableString')); }); it('returns null from getFeatureVariableJSON when optimizely object is not a valid instance', function() { @@ -9083,7 +9116,7 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(createdLogger.log); var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableJSON')); + assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableJSON')); }); }); }); @@ -9804,7 +9837,7 @@ describe('lib/optimizely', function() { describe('when the event processor onTerminated() method returns a promise that rejects', function() { beforeEach(function() { - eventProcessorStopPromise = Promise.reject(new Error('Failed to stop')); + eventProcessorStopPromise = Promise.reject(new Error(FAILED_TO_STOP)); eventProcessorStopPromise.catch(() => {}); mockEventProcessor.onTerminated.returns(eventProcessorStopPromise); const mockConfigManager = getMockProjectConfigManager({ @@ -9979,9 +10012,9 @@ describe('lib/optimizely', function() { var readyPromise = optlyInstance.onReady({ timeout: 500 }); clock.tick(501); return readyPromise.then(() => { - return Promise.reject(new Error('Promise should not have resolved')); + return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { - assert.equal(err.message, 'onReady timeout expired after 500 ms') + assert.equal(err.message, sprintf(ONREADY_TIMEOUT_EXPIRED, 500)); }); }); @@ -10003,7 +10036,7 @@ describe('lib/optimizely', function() { var readyPromise = optlyInstance.onReady(); clock.tick(300001); return readyPromise.then(() => { - return Promise.reject(new Error('Promise should not have resolved')); + return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { assert.equal(err.message, 'onReady timeout expired after 30000 ms') }); @@ -10027,7 +10060,7 @@ describe('lib/optimizely', function() { var readyPromise = optlyInstance.onReady({ timeout: 100 }); optlyInstance.close(); return readyPromise.then(() => { - return Promise.reject(new Error('Promise should not have resolved')); + return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { assert.equal(err.message, 'Instance closed') }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 4c4898c91..34fa116f6 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -53,9 +53,7 @@ import * as stringValidator from '../utils/string_value_validator'; import * as decision from '../core/decision'; import { - ERROR_MESSAGES, LOG_LEVEL, - LOG_MESSAGES, DECISION_SOURCES, DECISION_MESSAGES, FEATURE_VARIABLE_TYPES, @@ -68,6 +66,37 @@ import { Fn } from '../utils/type'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; +import { + FEATURE_NOT_IN_DATAFILE, + INVALID_EXPERIMENT_KEY, + INVALID_INPUT_FORMAT, + NO_EVENT_PROCESSOR, + ODP_EVENT_FAILED, + ODP_EVENT_FAILED_ODP_MANAGER_MISSING, + UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE, +} from '../error_messages'; +import { + EVENT_KEY_NOT_FOUND, + FEATURE_ENABLED_FOR_USER, + FEATURE_NOT_ENABLED_FOR_USER, + FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, + INVALID_CLIENT_ENGINE, + INVALID_DECIDE_OPTIONS, + INVALID_DEFAULT_DECIDE_OPTIONS, + INVALID_OBJECT, + NOT_ACTIVATING_USER, + NOT_TRACKING_USER, + SHOULD_NOT_DISPATCH_ACTIVATE, + TRACK_EVENT, + UNRECOGNIZED_DECIDE_OPTION, + UPDATED_OPTIMIZELY_CONFIG, + USER_RECEIVED_DEFAULT_VARIABLE_VALUE, + USER_RECEIVED_VARIABLE_VALUE, + VALID_USER_PROFILE_SERVICE, + VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, + VARIABLE_REQUESTED_WITH_WRONG_TYPE, +} from '../log_messages'; +import { INSTANCE_CLOSED } from '../exception_messages'; const MODULE_NAME = 'OPTIMIZELY'; @@ -103,7 +132,7 @@ export default class Optimizely implements Client { constructor(config: OptimizelyOptions) { let clientEngine = config.clientEngine; if (!clientEngine) { - config.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine); + config.logger.log(LOG_LEVEL.INFO, INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine); clientEngine = NODE_CLIENT_ENGINE; } @@ -117,7 +146,7 @@ export default class Optimizely implements Client { let decideOptionsArray = config.defaultDecideOptions ?? []; if (!Array.isArray(decideOptionsArray)) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.INVALID_DEFAULT_DECIDE_OPTIONS, MODULE_NAME); + this.logger.log(LOG_LEVEL.DEBUG, INVALID_DEFAULT_DECIDE_OPTIONS, MODULE_NAME); decideOptionsArray = []; } @@ -127,7 +156,7 @@ export default class Optimizely implements Client { if (OptimizelyDecideOption[option]) { defaultDecideOptions[option] = true; } else { - this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); + this.logger.log(LOG_LEVEL.WARNING, UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); } }); this.defaultDecideOptions = defaultDecideOptions; @@ -136,7 +165,7 @@ export default class Optimizely implements Client { this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, + UPDATED_OPTIMIZELY_CONFIG, MODULE_NAME, configObj.revision, configObj.projectId @@ -156,7 +185,7 @@ export default class Optimizely implements Client { try { if (userProfileServiceValidator.validate(config.userProfileService)) { userProfileService = config.userProfileService; - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_USER_PROFILE_SERVICE, MODULE_NAME); + this.logger.log(LOG_LEVEL.INFO, VALID_USER_PROFILE_SERVICE, MODULE_NAME); } } catch (ex) { this.logger.log(LOG_LEVEL.WARNING, ex.message); @@ -227,7 +256,7 @@ export default class Optimizely implements Client { activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'activate'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'activate'); return null; } @@ -248,7 +277,7 @@ export default class Optimizely implements Client { // If experiment is not set to 'Running' status, log accordingly and return variation key if (!projectConfig.isRunning(configObj, experimentKey)) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.SHOULD_NOT_DISPATCH_ACTIVATE, MODULE_NAME, experimentKey); + this.logger.log(LOG_LEVEL.DEBUG, SHOULD_NOT_DISPATCH_ACTIVATE, MODULE_NAME, experimentKey); return variationKey; } @@ -264,7 +293,7 @@ export default class Optimizely implements Client { return variationKey; } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); + this.logger.log(LOG_LEVEL.INFO, NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); this.errorHandler.handleError(ex); return null; } @@ -293,7 +322,7 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): void { if (!this.eventProcessor) { - this.logger.error(ERROR_MESSAGES.NO_EVENT_PROCESSOR); + this.logger.error(NO_EVENT_PROCESSOR); return; } @@ -334,12 +363,12 @@ export default class Optimizely implements Client { track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void { try { if (!this.eventProcessor) { - this.logger.error(ERROR_MESSAGES.NO_EVENT_PROCESSOR); + this.logger.error(NO_EVENT_PROCESSOR); return; } if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'track'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'track'); return; } @@ -353,8 +382,8 @@ export default class Optimizely implements Client { } if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { - this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.EVENT_KEY_NOT_FOUND, MODULE_NAME, eventKey); - this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); + this.logger.log(LOG_LEVEL.WARNING, EVENT_KEY_NOT_FOUND, MODULE_NAME, eventKey); + this.logger.log(LOG_LEVEL.WARNING, NOT_TRACKING_USER, MODULE_NAME, userId); return; } @@ -369,7 +398,7 @@ export default class Optimizely implements Client { clientVersion: this.clientVersion, configObj: configObj, }); - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId); + this.logger.log(LOG_LEVEL.INFO, TRACK_EVENT, MODULE_NAME, eventKey, userId); // TODO is it okay to not pass a projectConfig as second argument this.eventProcessor.process(conversionEvent); @@ -384,7 +413,7 @@ export default class Optimizely implements Client { } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); + this.logger.log(LOG_LEVEL.ERROR, NOT_TRACKING_USER, MODULE_NAME, userId); } } @@ -398,7 +427,7 @@ export default class Optimizely implements Client { getVariation(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getVariation'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getVariation'); return null; } @@ -414,7 +443,7 @@ export default class Optimizely implements Client { const experiment = configObj.experimentKeyMap[experimentKey]; if (!experiment || experiment.isRollout) { - this.logger.log(LOG_LEVEL.DEBUG, ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey); + this.logger.log(LOG_LEVEL.DEBUG, INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey); return null; } @@ -515,14 +544,14 @@ export default class Optimizely implements Client { if (stringInputs.hasOwnProperty('user_id')) { const userId = stringInputs['user_id']; if (typeof userId !== 'string' || userId === null || userId === 'undefined') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, 'user_id')); + throw new Error(sprintf(INVALID_INPUT_FORMAT, MODULE_NAME, 'user_id')); } delete stringInputs['user_id']; } Object.keys(stringInputs).forEach(key => { if (!stringValidator.validate(stringInputs[key as InputKey])) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, key)); + throw new Error(sprintf(INVALID_INPUT_FORMAT, MODULE_NAME, key)); } }); if (userAttributes) { @@ -546,7 +575,7 @@ export default class Optimizely implements Client { * @return {null} */ private notActivatingExperiment(experimentKey: string, userId: string): null { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); + this.logger.log(LOG_LEVEL.INFO, NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); return null; } @@ -574,7 +603,7 @@ export default class Optimizely implements Client { isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'isFeatureEnabled'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'isFeatureEnabled'); return false; } @@ -616,9 +645,9 @@ export default class Optimizely implements Client { } if (featureEnabled === true) { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); + this.logger.log(LOG_LEVEL.INFO, FEATURE_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); } else { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); + this.logger.log(LOG_LEVEL.INFO, FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); featureEnabled = false; } @@ -655,7 +684,7 @@ export default class Optimizely implements Client { try { const enabledFeatures: string[] = []; if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getEnabledFeatures'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getEnabledFeatures'); return enabledFeatures; } @@ -704,7 +733,7 @@ export default class Optimizely implements Client { ): FeatureVariableValue { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, null, userId, attributes); @@ -766,7 +795,7 @@ export default class Optimizely implements Client { if (variableType && variable.type !== variableType) { this.logger.log( LOG_LEVEL.WARNING, - LOG_MESSAGES.VARIABLE_REQUESTED_WITH_WRONG_TYPE, + VARIABLE_REQUESTED_WITH_WRONG_TYPE, MODULE_NAME, variableType, variable.type @@ -849,7 +878,7 @@ export default class Optimizely implements Client { variableValue = value; this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_RECEIVED_VARIABLE_VALUE, + USER_RECEIVED_VARIABLE_VALUE, MODULE_NAME, variableValue, variable.key, @@ -858,7 +887,7 @@ export default class Optimizely implements Client { } else { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, + FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, MODULE_NAME, featureKey, userId, @@ -868,7 +897,7 @@ export default class Optimizely implements Client { } else { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, + VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, MODULE_NAME, variable.key, variation.key @@ -877,7 +906,7 @@ export default class Optimizely implements Client { } else { this.logger.log( LOG_LEVEL.INFO, - LOG_MESSAGES.USER_RECEIVED_DEFAULT_VARIABLE_VALUE, + USER_RECEIVED_DEFAULT_VARIABLE_VALUE, MODULE_NAME, userId, variable.key, @@ -909,7 +938,7 @@ export default class Optimizely implements Client { ): boolean | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean'); return null; } return this.getFeatureVariableForType( @@ -948,7 +977,7 @@ export default class Optimizely implements Client { ): number | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble'); return null; } return this.getFeatureVariableForType( @@ -987,7 +1016,7 @@ export default class Optimizely implements Client { ): number | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger'); return null; } return this.getFeatureVariableForType( @@ -1026,7 +1055,7 @@ export default class Optimizely implements Client { ): string | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); return null; } return this.getFeatureVariableForType( @@ -1060,7 +1089,7 @@ export default class Optimizely implements Client { getFeatureVariableJSON(featureKey: string, variableKey: string, userId: string, attributes: UserAttributes): unknown { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); @@ -1088,7 +1117,7 @@ export default class Optimizely implements Client { ): { [variableKey: string]: unknown } | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getAllFeatureVariables'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getAllFeatureVariables'); return null; } @@ -1330,7 +1359,7 @@ export default class Optimizely implements Client { const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); const onClose = function() { - timeoutPromise.reject(new Error('Instance closed')); + timeoutPromise.reject(new Error(INSTANCE_CLOSED)); }; this.readyTimeouts[timeoutId] = { @@ -1398,7 +1427,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig(); if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decide'); + this.logger.log(LOG_LEVEL.INFO, INVALID_OBJECT, MODULE_NAME, 'decide'); return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); } @@ -1413,14 +1442,14 @@ export default class Optimizely implements Client { private getAllDecideOptions(options: OptimizelyDecideOption[]): { [key: string]: boolean } { const allDecideOptions = { ...this.defaultDecideOptions }; if (!Array.isArray(options)) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.INVALID_DECIDE_OPTIONS, MODULE_NAME); + this.logger.log(LOG_LEVEL.DEBUG, INVALID_DECIDE_OPTIONS, MODULE_NAME); } else { options.forEach(option => { // Filter out all provided decide options that are not in OptimizelyDecideOption[] if (OptimizelyDecideOption[option]) { allDecideOptions[option] = true; } else { - this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); + this.logger.log(LOG_LEVEL.WARNING, UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); } }); } @@ -1458,9 +1487,9 @@ export default class Optimizely implements Client { let decisionEventDispatched = false; if (flagEnabled) { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, key, userId); + this.logger.log(LOG_LEVEL.INFO, FEATURE_ENABLED_FOR_USER, MODULE_NAME, key, userId); } else { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, key, userId); + this.logger.log(LOG_LEVEL.INFO, FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, key, userId); } @@ -1544,7 +1573,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig() if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'decideForKeys'); return decisionMap; } if (keys.length === 0) { @@ -1560,7 +1589,7 @@ export default class Optimizely implements Client { for(const key of keys) { const feature = configObj.featureKeyMap[key]; if (!feature) { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key); + this.logger.log(LOG_LEVEL.ERROR, FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key); decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); continue } @@ -1614,7 +1643,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideAll'); + this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'decideAll'); return decisionMap; } @@ -1653,7 +1682,7 @@ export default class Optimizely implements Client { data?: Map<string, unknown> ): void { if (!this.odpManager) { - this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING); + this.logger.error(ODP_EVENT_FAILED_ODP_MANAGER_MISSING); return; } @@ -1661,7 +1690,7 @@ export default class Optimizely implements Client { const odpEvent = new OdpEvent(type || '', action, identifiers, data); this.odpManager.sendEvent(odpEvent); } catch (e) { - this.logger.error(ERROR_MESSAGES.ODP_EVENT_FAILED, e); + this.logger.error(ODP_EVENT_FAILED, e); } } /** @@ -1706,7 +1735,7 @@ export default class Optimizely implements Client { */ public getVuid(): string | undefined { if (!this.vuidManager) { - this.logger?.error('Unable to get VUID - VuidManager is not available'); + this.logger?.error(UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE); return undefined; } diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index fc72ffe0e..fbc9eb29b 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { assert } from 'chai'; import sinon from 'sinon'; @@ -26,13 +25,19 @@ import { createLogger } from '../plugins/logger'; import { createNotificationCenter } from '../notification_center'; import Optimizely from '../optimizely'; import errorHandler from '../plugins/error_handler'; -import { CONTROL_ATTRIBUTES, LOG_LEVEL, LOG_MESSAGES } from '../utils/enums'; +import { CONTROL_ATTRIBUTES, LOG_LEVEL } from '../utils/enums'; import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; import * as logger from '../plugins/logger'; +import { + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, +} from '../log_messages'; const getMockEventDispatcher = () => { const dispatcher = { @@ -461,7 +466,7 @@ describe('lib/optimizely_user_context', function() { assert.equal( true, decision.reasons.includes( - sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) ) ); @@ -487,7 +492,7 @@ describe('lib/optimizely_user_context', function() { assert.equal( true, decision.reasons.includes( - sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) ) ); @@ -518,7 +523,7 @@ describe('lib/optimizely_user_context', function() { assert.equal( true, decision.reasons.includes( - sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) ) ); @@ -561,7 +566,7 @@ describe('lib/optimizely_user_context', function() { decisionEventDispatched: true, reasons: [ sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId @@ -597,7 +602,7 @@ describe('lib/optimizely_user_context', function() { true, decision.reasons.includes( sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, @@ -645,7 +650,7 @@ describe('lib/optimizely_user_context', function() { decisionEventDispatched: true, reasons: [ sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, @@ -778,7 +783,7 @@ describe('lib/optimizely_user_context', function() { assert.equal( true, decision.reasons.includes( - sprintf(LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, featureKey, userId) + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, featureKey, userId) ) ); }); @@ -801,7 +806,7 @@ describe('lib/optimizely_user_context', function() { true, decision.reasons.includes( sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, userId @@ -828,7 +833,7 @@ describe('lib/optimizely_user_context', function() { true, decision.reasons.includes( sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, userId diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index e688c588a..6550fa0c6 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -30,7 +30,7 @@ async function mockRequireAsyncStorage() { M._load = (uri: string, parent: string) => { if (uri === '@react-native-async-storage/async-storage') { if (isAsyncStorageAvailable) return { default: {} }; - throw new Error('Module not found: @react-native-async-storage/async-storage'); + throw new Error("Module not found: @react-native-async-storage/async-storage"); } return M._load_original(uri, parent); }; @@ -157,7 +157,7 @@ describe('createPollingConfigManager', () => { }; expect(() => createPollingProjectConfigManager(config)).toThrowError( - 'Module not found: @react-native-async-storage/async-storage' + "Module not found: @react-native-async-storage/async-storage" ); isAsyncStorageAvailable = true; }); diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index f7223fc00..bde704029 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -23,6 +23,14 @@ import { RequestHandler, AbortableRequest, Headers, Response } from '../utils/ht import { Repeater } from '../utils/repeater/repeater'; import { Consumer, Fn } from '../utils/type'; import { isSuccessStatusCode } from '../utils/http_request_handler/http_util'; +import { DATAFILE_MANAGER_STOPPED, FAILED_TO_FETCH_DATAFILE } from '../exception_messages'; +import { DATAFILE_FETCH_REQUEST_FAILED, ERROR_FETCHING_DATAFILE } from '../error_messages'; +import { + ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN, + MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS, + RESPONSE_STATUS_CODE, + SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, +} from '../log_messages'; export class PollingDatafileManager extends BaseService implements DatafileManager { private requestHandler: RequestHandler; @@ -94,11 +102,10 @@ export class PollingDatafileManager extends BaseService implements DatafileManag } if (this.isNew() || this.isStarting()) { - // TOOD: replace message with imported constants - this.startPromise.reject(new Error('Datafile manager stopped before it could be started')); + this.startPromise.reject(new Error(DATAFILE_MANAGER_STOPPED)); } - - this.logger?.debug('Datafile manager stopped'); + + this.logger?.debug(DATAFILE_MANAGER_STOPPED); this.state = ServiceState.Terminated; this.repeater.stop(); this.currentRequest?.abort(); @@ -109,8 +116,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag private handleInitFailure(): void { this.state = ServiceState.Failed; this.repeater.stop(); - // TODO: replace message with imported constants - const error = new Error('Failed to fetch datafile'); + const error = new Error(FAILED_TO_FETCH_DATAFILE); this.startPromise.reject(error); this.stopPromise.reject(error); } @@ -120,11 +126,10 @@ export class PollingDatafileManager extends BaseService implements DatafileManag return; } - // TODO: replace message with imported constants if (errorOrStatus instanceof Error) { - this.logger?.error('Error fetching datafile: %s', errorOrStatus.message, errorOrStatus); + this.logger?.error(ERROR_FETCHING_DATAFILE, errorOrStatus.message, errorOrStatus); } else { - this.logger?.error(`Datafile fetch request failed with status: ${errorOrStatus}`); + this.logger?.error(DATAFILE_FETCH_REQUEST_FAILED, errorOrStatus); } if(this.isStarting() && this.initRetryRemaining !== undefined) { @@ -170,11 +175,11 @@ export class PollingDatafileManager extends BaseService implements DatafileManag } if (this.datafileAccessToken) { - this.logger?.debug('Adding Authorization header with Bearer Token'); + this.logger?.debug(ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN); headers['Authorization'] = `Bearer ${this.datafileAccessToken}`; } - this.logger?.debug('Making datafile request to url %s with headers: %s', this.datafileUrl, () => JSON.stringify(headers)); + this.logger?.debug(MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS, this.datafileUrl, () => JSON.stringify(headers)); return this.requestHandler.makeRequest(this.datafileUrl, headers, 'GET'); } @@ -201,7 +206,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag } private getDatafileFromResponse(response: Response): string | undefined{ - this.logger?.debug('Response status code: %s', response.statusCode); + this.logger?.debug(RESPONSE_STATUS_CODE, response.statusCode); if (response.statusCode === 304) { return undefined; } @@ -212,7 +217,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag const lastModifiedHeader = headers['last-modified'] || headers['Last-Modified']; if (lastModifiedHeader !== undefined) { this.lastResponseLastModified = lastModifiedHeader; - this.logger?.debug('Saved last modified header value from response: %s', this.lastResponseLastModified); + this.logger?.debug(SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, this.lastResponseLastModified); } } diff --git a/lib/project_config/project_config.tests.js b/lib/project_config/project_config.tests.js index c49a75dad..6bfc34d67 100644 --- a/lib/project_config/project_config.tests.js +++ b/lib/project_config/project_config.tests.js @@ -21,10 +21,11 @@ import { getLogger } from '../modules/logging'; import fns from '../utils/fns'; import projectConfig from './project_config'; -import { ERROR_MESSAGES, FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; +import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; import * as loggerPlugin from '../plugins/logger'; import testDatafile from '../tests/test_data'; import configValidator from '../utils/config_validator'; +import { INVALID_EXPERIMENT_ID, INVALID_EXPERIMENT_KEY } from '../error_messages'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var logger = getLogger(); @@ -300,7 +301,7 @@ describe('lib/core/project_config', function() { it('should throw error for invalid experiment key in getExperimentId', function() { assert.throws(function() { projectConfig.getExperimentId(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }, sprintf(INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); }); it('should retrieve layer ID for valid experiment key in getLayerId', function() { @@ -310,7 +311,7 @@ describe('lib/core/project_config', function() { it('should throw error for invalid experiment key in getLayerId', function() { assert.throws(function() { projectConfig.getLayerId(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey')); }); it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { @@ -360,7 +361,7 @@ describe('lib/core/project_config', function() { it('should throw error for invalid experiment key in getExperimentStatus', function() { assert.throws(function() { projectConfig.getExperimentStatus(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }, sprintf(INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); }); it('should return true if experiment status is set to Running in isActive', function() { @@ -396,7 +397,7 @@ describe('lib/core/project_config', function() { it('should throw error for invalid experient key in getTrafficAllocation', function() { assert.throws(function() { projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); }); describe('#getVariationIdFromExperimentAndVariationKey', function() { @@ -684,7 +685,7 @@ describe('lib/core/project_config', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); assert.throws(function() { projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); }); it('should return experiment audienceIds if experiment has no audienceConditions', function() { diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index a9b618894..781470ab2 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -15,7 +15,7 @@ */ import { find, objectEntries, objectValues, sprintf, keyBy } from '../utils/fns'; -import { ERROR_MESSAGES, LOG_LEVEL, LOG_MESSAGES, FEATURE_VARIABLE_TYPES } from '../utils/enums'; +import { LOG_LEVEL, FEATURE_VARIABLE_TYPES } from '../utils/enums'; import configValidator from '../utils/config_validator'; import { LogHandler } from '../modules/logging'; @@ -36,6 +36,18 @@ import { } from '../shared_types'; import { OdpConfig, OdpIntegrationConfig } from '../odp/odp_config'; import { Transformer } from '../utils/type'; +import { + EXPERIMENT_KEY_NOT_IN_DATAFILE, + FEATURE_NOT_IN_DATAFILE, + INVALID_EXPERIMENT_ID, + INVALID_EXPERIMENT_KEY, + MISSING_INTEGRATION_KEY, + UNABLE_TO_CAST_VALUE, + UNRECOGNIZED_ATTRIBUTE, + VARIABLE_KEY_NOT_IN_DATAFILE, + VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, +} from '../error_messages'; +import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from '../log_messages'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type @@ -199,7 +211,7 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.integrations.forEach(integration => { if (!('key' in integration)) { - throw new Error(sprintf(ERROR_MESSAGES.MISSING_INTEGRATION_KEY, MODULE_NAME)); + throw new Error(sprintf(MISSING_INTEGRATION_KEY, MODULE_NAME)); } if (integration.key === 'odp') { @@ -347,7 +359,7 @@ function isLogicalOperator(condition: string): boolean { export const getExperimentId = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new Error(sprintf(INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); } return experiment.id; }; @@ -362,7 +374,7 @@ export const getExperimentId = function(projectConfig: ProjectConfig, experiment export const getLayerId = function(projectConfig: ProjectConfig, experimentId: string): string { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); } return experiment.layerId; }; @@ -395,7 +407,7 @@ export const getAttributeId = function( return attributeKey; } - logger.log(LOG_LEVEL.DEBUG, ERROR_MESSAGES.UNRECOGNIZED_ATTRIBUTE, MODULE_NAME, attributeKey); + logger.log(LOG_LEVEL.DEBUG, UNRECOGNIZED_ATTRIBUTE, MODULE_NAME, attributeKey); return null; }; @@ -423,7 +435,7 @@ export const getEventId = function(projectConfig: ProjectConfig, eventKey: strin export const getExperimentStatus = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new Error(sprintf(INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); } return experiment.status; }; @@ -465,7 +477,7 @@ export const getExperimentAudienceConditions = function( ): Array<string | string[]> { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); } return experiment.audienceConditions || experiment.audienceIds; @@ -534,7 +546,7 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper } } - throw new Error(sprintf(ERROR_MESSAGES.EXPERIMENT_KEY_NOT_IN_DATAFILE, MODULE_NAME, experimentKey)); + throw new Error(sprintf(EXPERIMENT_KEY_NOT_IN_DATAFILE, MODULE_NAME, experimentKey)); }; /** @@ -547,7 +559,7 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper export const getTrafficAllocation = function(projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); } return experiment.trafficAllocation; }; @@ -572,7 +584,7 @@ export const getExperimentFromId = function( } } - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId); + logger.log(LOG_LEVEL.ERROR, INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId); return null; }; @@ -621,7 +633,7 @@ export const getFeatureFromKey = function( } } - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); + logger.log(LOG_LEVEL.ERROR, FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); return null; }; @@ -644,13 +656,13 @@ export const getVariableForFeature = function( ): FeatureVariable | null { const feature = projectConfig.featureKeyMap[featureKey]; if (!feature) { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); + logger.log(LOG_LEVEL.ERROR, FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); return null; } const variable = feature.variableKeyMap[variableKey]; if (!variable) { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.VARIABLE_KEY_NOT_IN_DATAFILE, MODULE_NAME, variableKey, featureKey); + logger.log(LOG_LEVEL.ERROR, VARIABLE_KEY_NOT_IN_DATAFILE, MODULE_NAME, variableKey, featureKey); return null; } @@ -680,7 +692,7 @@ export const getVariableValueForVariation = function( } if (!projectConfig.variationVariableUsageMap.hasOwnProperty(variation.id)) { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, MODULE_NAME, variation.id); + logger.log(LOG_LEVEL.ERROR, VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, MODULE_NAME, variation.id); return null; } @@ -716,7 +728,7 @@ export const getTypeCastValue = function( switch (variableType) { case FEATURE_VARIABLE_TYPES.BOOLEAN: if (variableValue !== 'true' && variableValue !== 'false') { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } else { castValue = variableValue === 'true'; @@ -726,7 +738,7 @@ export const getTypeCastValue = function( case FEATURE_VARIABLE_TYPES.INTEGER: castValue = parseInt(variableValue, 10); if (isNaN(castValue)) { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } break; @@ -734,7 +746,7 @@ export const getTypeCastValue = function( case FEATURE_VARIABLE_TYPES.DOUBLE: castValue = parseFloat(variableValue); if (isNaN(castValue)) { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } break; @@ -743,7 +755,7 @@ export const getTypeCastValue = function( try { castValue = JSON.parse(variableValue); } catch (e) { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); castValue = null; } break; @@ -821,9 +833,9 @@ export const tryCreatingProjectConfig = function( if (config.jsonSchemaValidator) { config.jsonSchemaValidator(newDatafileObj); - config.logger?.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_DATAFILE, MODULE_NAME); + config.logger?.log(LOG_LEVEL.INFO, VALID_DATAFILE, MODULE_NAME); } else { - config.logger?.log(LOG_LEVEL.INFO, LOG_MESSAGES.SKIPPING_JSON_VALIDATION, MODULE_NAME); + config.logger?.log(LOG_LEVEL.INFO, SKIPPING_JSON_VALIDATION, MODULE_NAME); } const createProjectConfigArgs = [newDatafileObj]; diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts index 682c2bede..7bed978ef 100644 --- a/lib/project_config/project_config_manager.spec.ts +++ b/lib/project_config/project_config_manager.spec.ts @@ -127,7 +127,7 @@ describe('ProjectConfigManagerImpl', () => { }); it('should resolve onRunning() even if datafileManger.onRunning() rejects', async () => { - const onRunning = Promise.reject(new Error('onRunning error')); + const onRunning = Promise.reject(new Error("onRunning error")); const datafileManager = getMockDatafileManager({ onRunning, }); diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index b71ef1f39..81ee87b78 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -22,6 +22,12 @@ import { scheduleMicrotask } from '../utils/microtask'; import { Service, ServiceState, BaseService } from '../service'; import { Consumer, Fn, Transformer } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; +import { + DATAFILE_MANAGER_FAILED_TO_START, + DATAFILE_MANAGER_STOPPED, + YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE, + YOU_MUST_PROVIDE_DATAFILE_IN_SSR, +} from '../exception_messages'; interface ProjectConfigManagerConfig { // TODO: Don't use object type @@ -78,9 +84,9 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf if (!this.datafile && !this.datafileManager) { const errorMessage = this.isSsr - ? 'You must provide datafile in SSR' - : 'You must provide at least one of sdkKey or datafile'; - // TODO: replace message with imported constants + ? YOU_MUST_PROVIDE_DATAFILE_IN_SSR + : YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE; + this.handleInitError(new Error(errorMessage)); return; } @@ -113,8 +119,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf } private handleDatafileManagerError(err: Error): void { - // TODO: replace message with imported constants - this.logger?.error('datafile manager failed to start', err); + this.logger?.error(DATAFILE_MANAGER_FAILED_TO_START, err); // If datafile manager onRunning() promise is rejected, and the project config manager // is still in starting state, that means a datafile was not provided in cofig or was invalid, @@ -202,7 +207,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf if (this.isNew() || this.isStarting()) { // TOOD: replace message with imported constants - this.startPromise.reject(new Error('Datafile manager stopped before it could be started')); + this.startPromise.reject(new Error(DATAFILE_MANAGER_STOPPED)); } this.state = ServiceState.Stopping; diff --git a/lib/utils/attributes_validator/index.tests.js b/lib/utils/attributes_validator/index.tests.js index d98e8fdb0..ed79d9470 100644 --- a/lib/utils/attributes_validator/index.tests.js +++ b/lib/utils/attributes_validator/index.tests.js @@ -17,7 +17,7 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import * as attributesValidator from './'; -import { ERROR_MESSAGES } from '../enums'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from '../../error_messages'; describe('lib/utils/attributes_validator', function() { describe('APIs', function() { @@ -30,13 +30,13 @@ describe('lib/utils/attributes_validator', function() { var attributesArray = ['notGonnaWork']; assert.throws(function() { attributesValidator.validate(attributesArray); - }, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); it('should throw an error if attributes is null', function() { assert.throws(function() { attributesValidator.validate(null); - }, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); it('should throw an error if attributes is a function', function() { @@ -45,7 +45,7 @@ describe('lib/utils/attributes_validator', function() { } assert.throws(function() { attributesValidator.validate(invalidInput); - }, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); it('should throw an error if attributes contains a key with an undefined value', function() { @@ -55,7 +55,7 @@ describe('lib/utils/attributes_validator', function() { assert.throws(function() { attributesValidator.validate(attributes); - }, sprintf(ERROR_MESSAGES.UNDEFINED_ATTRIBUTE, 'ATTRIBUTES_VALIDATOR', attributeKey)); + }, sprintf(UNDEFINED_ATTRIBUTE, 'ATTRIBUTES_VALIDATOR', attributeKey)); }); }); diff --git a/lib/utils/attributes_validator/index.ts b/lib/utils/attributes_validator/index.ts index 5f33e85d4..255b99419 100644 --- a/lib/utils/attributes_validator/index.ts +++ b/lib/utils/attributes_validator/index.ts @@ -17,7 +17,7 @@ import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import fns from '../../utils/fns'; -import { ERROR_MESSAGES } from '../enums'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from '../../error_messages'; const MODULE_NAME = 'ATTRIBUTES_VALIDATOR'; @@ -32,12 +32,12 @@ export function validate(attributes: unknown): boolean { if (typeof attributes === 'object' && !Array.isArray(attributes) && attributes !== null) { Object.keys(attributes).forEach(function(key) { if (typeof (attributes as ObjectWithUnknownProperties)[key] === 'undefined') { - throw new Error(sprintf(ERROR_MESSAGES.UNDEFINED_ATTRIBUTE, MODULE_NAME, key)); + throw new Error(sprintf(UNDEFINED_ATTRIBUTE, MODULE_NAME, key)); } }); return true; } else { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, MODULE_NAME)); + throw new Error(sprintf(INVALID_ATTRIBUTES, MODULE_NAME)); } } diff --git a/lib/utils/config_validator/index.tests.js b/lib/utils/config_validator/index.tests.js index 9ff0a32c5..b7a8711c7 100644 --- a/lib/utils/config_validator/index.tests.js +++ b/lib/utils/config_validator/index.tests.js @@ -17,8 +17,15 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import configValidator from './'; -import { ERROR_MESSAGES } from '../enums'; import testData from '../../tests/test_data'; +import { + INVALID_DATAFILE_MALFORMED, + INVALID_DATAFILE_VERSION, + INVALID_ERROR_HANDLER, + INVALID_EVENT_DISPATCHER, + INVALID_LOGGER, + NO_DATAFILE_SPECIFIED, +} from '../../error_messages'; describe('lib/utils/config_validator', function() { describe('APIs', function() { @@ -28,7 +35,7 @@ describe('lib/utils/config_validator', function() { configValidator.validate({ errorHandler: {}, }); - }, sprintf(ERROR_MESSAGES.INVALID_ERROR_HANDLER, 'CONFIG_VALIDATOR')); + }, sprintf(INVALID_ERROR_HANDLER, 'CONFIG_VALIDATOR')); }); it('should complain if the provided event dispatcher is invalid', function() { @@ -36,7 +43,7 @@ describe('lib/utils/config_validator', function() { configValidator.validate({ eventDispatcher: {}, }); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_DISPATCHER, 'CONFIG_VALIDATOR')); + }, sprintf(INVALID_EVENT_DISPATCHER, 'CONFIG_VALIDATOR')); }); it('should complain if the provided logger is invalid', function() { @@ -44,25 +51,25 @@ describe('lib/utils/config_validator', function() { configValidator.validate({ logger: {}, }); - }, sprintf(ERROR_MESSAGES.INVALID_LOGGER, 'CONFIG_VALIDATOR')); + }, sprintf(INVALID_LOGGER, 'CONFIG_VALIDATOR')); }); it('should complain if datafile is not provided', function() { assert.throws(function() { configValidator.validateDatafile(); - }, sprintf(ERROR_MESSAGES.NO_DATAFILE_SPECIFIED, 'CONFIG_VALIDATOR')); + }, sprintf(NO_DATAFILE_SPECIFIED, 'CONFIG_VALIDATOR')); }); it('should complain if datafile is malformed', function() { assert.throws(function() { configValidator.validateDatafile('abc'); - }, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_MALFORMED, 'CONFIG_VALIDATOR')); + }, sprintf(INVALID_DATAFILE_MALFORMED, 'CONFIG_VALIDATOR')); }); it('should complain if datafile version is not supported', function() { assert.throws(function() { configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())); - }, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_VERSION, 'CONFIG_VALIDATOR', '5')); + }, sprintf(INVALID_DATAFILE_VERSION, 'CONFIG_VALIDATOR', '5')); }); it('should not complain if datafile is valid', function() { diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index a273121cc..12e0ca0d9 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -17,9 +17,17 @@ import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import { - ERROR_MESSAGES, DATAFILE_VERSIONS, } from '../enums'; +import { + INVALID_CONFIG, + INVALID_DATAFILE_MALFORMED, + INVALID_DATAFILE_VERSION, + INVALID_ERROR_HANDLER, + INVALID_EVENT_DISPATCHER, + INVALID_LOGGER, + NO_DATAFILE_SPECIFIED, +} from '../../error_messages'; const MODULE_NAME = 'CONFIG_VALIDATOR'; const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE_VERSIONS.V4]; @@ -40,17 +48,17 @@ export const validate = function(config: unknown): boolean { const eventDispatcher = configObj['eventDispatcher']; const logger = configObj['logger']; if (errorHandler && typeof (errorHandler as ObjectWithUnknownProperties)['handleError'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_ERROR_HANDLER, MODULE_NAME)); + throw new Error(sprintf(INVALID_ERROR_HANDLER, MODULE_NAME)); } if (eventDispatcher && typeof (eventDispatcher as ObjectWithUnknownProperties)['dispatchEvent'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EVENT_DISPATCHER, MODULE_NAME)); + throw new Error(sprintf(INVALID_EVENT_DISPATCHER, MODULE_NAME)); } if (logger && typeof (logger as ObjectWithUnknownProperties)['log'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_LOGGER, MODULE_NAME)); + throw new Error(sprintf(INVALID_LOGGER, MODULE_NAME)); } return true; } - throw new Error(sprintf(ERROR_MESSAGES.INVALID_CONFIG, MODULE_NAME)); + throw new Error(sprintf(INVALID_CONFIG, MODULE_NAME)); } /** @@ -65,19 +73,19 @@ export const validate = function(config: unknown): boolean { // eslint-disable-next-line export const validateDatafile = function(datafile: unknown): any { if (!datafile) { - throw new Error(sprintf(ERROR_MESSAGES.NO_DATAFILE_SPECIFIED, MODULE_NAME)); + throw new Error(sprintf(NO_DATAFILE_SPECIFIED, MODULE_NAME)); } if (typeof datafile === 'string') { // Attempt to parse the datafile string try { datafile = JSON.parse(datafile); } catch (ex) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_DATAFILE_MALFORMED, MODULE_NAME)); + throw new Error(sprintf(INVALID_DATAFILE_MALFORMED, MODULE_NAME)); } } if (typeof datafile === 'object' && !Array.isArray(datafile) && datafile !== null) { if (SUPPORTED_VERSIONS.indexOf(datafile['version' as keyof unknown]) === -1) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_DATAFILE_VERSION, MODULE_NAME, datafile['version' as keyof unknown])); + throw new Error(sprintf(INVALID_DATAFILE_VERSION, MODULE_NAME, datafile['version' as keyof unknown])); } } diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 551fa6b98..1c867fbbf 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -25,185 +25,6 @@ export const LOG_LEVEL = { ERROR: 4, }; -export const ERROR_MESSAGES = { - BROWSER_ODP_MANAGER_INITIALIZATION_FAILED: '%s: Error initializing Browser ODP Manager.', - CONDITION_EVALUATOR_ERROR: '%s: Error evaluating audience condition of type %s: %s', - DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', - EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', - FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', - FETCH_SEGMENTS_FAILED_NETWORK_ERROR: '%s: Audience segments fetch failed. (network error)', - FETCH_SEGMENTS_FAILED_DECODE_ERROR: '%s: Audience segments fetch failed. (decode error)', - IMPROPERLY_FORMATTED_EXPERIMENT: '%s: Experiment key %s is improperly formatted.', - INVALID_ATTRIBUTES: '%s: Provided attributes are in an invalid format.', - INVALID_BUCKETING_ID: '%s: Unable to generate hash for bucketing ID %s: %s', - INVALID_DATAFILE: '%s: Datafile is invalid - property %s: %s', - INVALID_DATAFILE_MALFORMED: '%s: Datafile is invalid because it is malformed.', - INVALID_CONFIG: '%s: Provided Optimizely config is in an invalid format.', - INVALID_JSON: '%s: JSON object is not valid.', - INVALID_ERROR_HANDLER: '%s: Provided "errorHandler" is in an invalid format.', - INVALID_EVENT_DISPATCHER: '%s: Provided "eventDispatcher" is in an invalid format.', - INVALID_EVENT_TAGS: '%s: Provided event tags are in an invalid format.', - INVALID_EXPERIMENT_KEY: '%s: Experiment key %s is not in datafile. It is either invalid, paused, or archived.', - INVALID_EXPERIMENT_ID: '%s: Experiment ID %s is not in datafile.', - INVALID_GROUP_ID: '%s: Group ID %s is not in datafile.', - INVALID_LOGGER: '%s: Provided "logger" is in an invalid format.', - INVALID_ROLLOUT_ID: '%s: Invalid rollout ID %s attached to feature %s', - INVALID_USER_ID: '%s: Provided user ID is in an invalid format.', - INVALID_USER_PROFILE_SERVICE: '%s: Provided user profile service instance is in an invalid format: %s.', - LOCAL_STORAGE_DOES_NOT_EXIST: 'Error accessing window localStorage.', - MISSING_INTEGRATION_KEY: '%s: Integration key missing from datafile. All integrations should include a key.', - NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', - NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', - NO_EVENT_PROCESSOR: 'No event processor is provided', - NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', - ODP_CONFIG_NOT_AVAILABLE: '%s: ODP is not integrated to the project.', - ODP_EVENT_FAILED: 'ODP event send failed.', - ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING: - '%s: ODP unable to fetch qualified segments (Segments Manager not initialized).', - ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING: - '%s: ODP identify event %s is not dispatched (Event Manager not instantiated).', - ODP_INITIALIZATION_FAILED: '%s: ODP failed to initialize.', - ODP_INVALID_DATA: '%s: ODP data is not valid', - ODP_EVENT_FAILED_ODP_MANAGER_MISSING: '%s: ODP Event failed to send. (ODP Manager not initialized).', - ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING: - '%s: ODP failed to Fetch Qualified Segments. (ODP Manager not initialized).', - ODP_IDENTIFY_USER_FAILED_ODP_MANAGER_MISSING: '%s: ODP failed to Identify User. (ODP Manager not initialized).', - ODP_IDENTIFY_USER_FAILED_USER_CONTEXT_INITIALIZATION: - '%s: ODP failed to Identify User. (Failed during User Context Initialization).', - ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING: - '%s: ODP Manager failed to update OdpConfig settings for internal event manager. (Event Manager not initialized).', - ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING: - '%s: ODP Manager failed to update OdpConfig settings for internal segments manager. (Segments Manager not initialized).', - ODP_NOT_ENABLED: 'ODP is not enabled', - ODP_NOT_INTEGRATED: '%s: ODP is not integrated', - ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING: - '%s: ODP send event %s was not dispatched (Event Manager not instantiated).', - ODP_SEND_EVENT_FAILED_UID_MISSING: '%s: ODP send event %s was not dispatched (No valid user identifier provided).', - ODP_SEND_EVENT_FAILED_VUID_MISSING: '%s: ODP send event %s was not dispatched (Unable to fetch VUID).', - ODP_VUID_INITIALIZATION_FAILED: '%s: ODP VUID initialization failed.', - ODP_VUID_REGISTRATION_FAILED: '%s: ODP VUID failed to be registered.', - ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING: '%s: ODP register vuid failed. (Event Manager not instantiated).', - UNDEFINED_ATTRIBUTE: '%s: Provided attribute: %s has an undefined value.', - UNRECOGNIZED_ATTRIBUTE: '%s: Unrecognized attribute %s provided. Pruning before sending event to Optimizely.', - UNABLE_TO_CAST_VALUE: '%s: Unable to cast value %s to type %s, returning null.', - USER_NOT_IN_FORCED_VARIATION: '%s: User %s is not in the forced variation map. Cannot remove their forced variation.', - USER_PROFILE_LOOKUP_ERROR: '%s: Error while looking up user profile for user ID "%s": %s.', - USER_PROFILE_SAVE_ERROR: '%s: Error while saving user profile for user ID "%s": %s.', - VARIABLE_KEY_NOT_IN_DATAFILE: '%s: Variable with key "%s" associated with feature with key "%s" is not in datafile.', - VARIATION_ID_NOT_IN_DATAFILE: '%s: No variation ID %s defined in datafile for experiment %s.', - VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT: '%s: Variation ID %s is not in the datafile.', - INVALID_INPUT_FORMAT: '%s: Provided %s is in an invalid format.', - INVALID_DATAFILE_VERSION: '%s: This version of the JavaScript SDK does not support the given datafile version: %s', - INVALID_VARIATION_KEY: '%s: Provided variation key is in an invalid format.', -}; - -export const LOG_MESSAGES = { - ACTIVATE_USER: '%s: Activating user %s in experiment %s.', - DISPATCH_CONVERSION_EVENT: '%s: Dispatching conversion event to URL %s with params %s.', - DISPATCH_IMPRESSION_EVENT: '%s: Dispatching impression event to URL %s with params %s.', - DEPRECATED_EVENT_VALUE: '%s: Event value is deprecated in %s call.', - EVENT_KEY_NOT_FOUND: '%s: Event key %s is not in datafile.', - EXPERIMENT_NOT_RUNNING: '%s: Experiment %s is not running.', - FEATURE_ENABLED_FOR_USER: '%s: Feature %s is enabled for user %s.', - FEATURE_NOT_ENABLED_FOR_USER: '%s: Feature %s is not enabled for user %s.', - FEATURE_HAS_NO_EXPERIMENTS: '%s: Feature %s is not attached to any experiments.', - FAILED_TO_PARSE_VALUE: '%s: Failed to parse event value "%s" from event tags.', - FAILED_TO_PARSE_REVENUE: '%s: Failed to parse revenue value "%s" from event tags.', - FORCED_BUCKETING_FAILED: '%s: Variation key %s is not in datafile. Not activating user %s.', - INVALID_OBJECT: '%s: Optimizely object is not valid. Failing %s.', - INVALID_CLIENT_ENGINE: '%s: Invalid client engine passed: %s. Defaulting to node-sdk.', - INVALID_DEFAULT_DECIDE_OPTIONS: '%s: Provided default decide options is not an array.', - INVALID_DECIDE_OPTIONS: '%s: Provided decide options is not an array. Using default decide options.', - INVALID_VARIATION_ID: '%s: Bucketed into an invalid variation ID. Returning null.', - NOTIFICATION_LISTENER_EXCEPTION: '%s: Notification listener for (%s) threw exception: %s', - NO_ROLLOUT_EXISTS: '%s: There is no rollout of feature %s.', - NOT_ACTIVATING_USER: '%s: Not activating user %s for experiment %s.', - NOT_TRACKING_USER: '%s: Not tracking user %s.', - ODP_DISABLED: 'ODP Disabled.', - ODP_IDENTIFY_FAILED_ODP_DISABLED: '%s: ODP identify event for user %s is not dispatched (ODP disabled).', - ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED: '%s: ODP identify event %s is not dispatched (ODP not integrated).', - ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED: - '%s: sendOdpEvent failed to parse through and convert fs_user_id aliases', - PARSED_REVENUE_VALUE: '%s: Parsed revenue value "%s" from event tags.', - PARSED_NUMERIC_VALUE: '%s: Parsed event value "%s" from event tags.', - RETURNING_STORED_VARIATION: - '%s: Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.', - ROLLOUT_HAS_NO_EXPERIMENTS: '%s: Rollout of feature %s has no experiments', - SAVED_USER_VARIATION: '%s: Saved user profile for user "%s".', - UPDATED_USER_VARIATION: '%s: Updated variation "%s" of experiment "%s" for user "%s".', - SAVED_VARIATION_NOT_FOUND: - '%s: User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.', - SHOULD_NOT_DISPATCH_ACTIVATE: '%s: Experiment %s is not in "Running" state. Not activating user.', - SKIPPING_JSON_VALIDATION: '%s: Skipping JSON schema validation.', - TRACK_EVENT: '%s: Tracking event %s for user %s.', - UNRECOGNIZED_DECIDE_OPTION: '%s: Unrecognized decide option %s provided.', - USER_ASSIGNED_TO_EXPERIMENT_BUCKET: '%s: Assigned bucket %s to user with bucketing ID %s.', - USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP: '%s: User %s is in experiment %s of group %s.', - USER_BUCKETED_INTO_TARGETING_RULE: '%s: User %s bucketed into targeting rule %s.', - USER_IN_FEATURE_EXPERIMENT: '%s: User %s is in variation %s of experiment %s on the feature %s.', - USER_IN_ROLLOUT: '%s: User %s is in rollout of feature %s.', - USER_NOT_BUCKETED_INTO_EVERYONE_TARGETING_RULE: - '%s: User %s not bucketed into everyone targeting rule due to traffic allocation.', - USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP: '%s: User %s is not in experiment %s of group %s.', - USER_NOT_BUCKETED_INTO_ANY_EXPERIMENT_IN_GROUP: '%s: User %s is not in any experiment of group %s.', - USER_NOT_BUCKETED_INTO_TARGETING_RULE: - '%s User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.', - USER_NOT_IN_FEATURE_EXPERIMENT: '%s: User %s is not in any experiment on the feature %s.', - USER_NOT_IN_ROLLOUT: '%s: User %s is not in rollout of feature %s.', - USER_FORCED_IN_VARIATION: '%s: User %s is forced in variation %s.', - USER_MAPPED_TO_FORCED_VARIATION: '%s: Set variation %s for experiment %s and user %s in the forced variation map.', - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE: '%s: User %s does not meet conditions for targeting rule %s.', - USER_MEETS_CONDITIONS_FOR_TARGETING_RULE: '%s: User %s meets conditions for targeting rule %s.', - USER_HAS_VARIATION: '%s: User %s is in variation %s of experiment %s.', - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED: - 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED: - 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID: - 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID: - 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_VARIATION: '%s: Variation %s is mapped to experiment %s and user %s in the forced variation map.', - USER_HAS_NO_VARIATION: '%s: User %s is in no variation of experiment %s.', - USER_HAS_NO_FORCED_VARIATION: '%s: User %s is not in the forced variation map.', - USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT: '%s: No experiment %s mapped to user %s in the forced variation map.', - USER_NOT_IN_ANY_EXPERIMENT: '%s: User %s is not in any experiment of group %s.', - USER_NOT_IN_EXPERIMENT: '%s: User %s does not meet conditions to be in experiment %s.', - USER_RECEIVED_DEFAULT_VARIABLE_VALUE: - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE: - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE: - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - USER_RECEIVED_VARIABLE_VALUE: '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - VALID_DATAFILE: '%s: Datafile is valid.', - VALID_USER_PROFILE_SERVICE: '%s: Valid user profile service provided.', - VARIATION_REMOVED_FOR_USER: '%s: Variation mapped to experiment %s has been removed for user %s.', - VARIABLE_REQUESTED_WITH_WRONG_TYPE: - '%s: Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.', - VALID_BUCKETING_ID: '%s: BucketingId is valid: "%s"', - BUCKETING_ID_NOT_STRING: '%s: BucketingID attribute is not a string. Defaulted to userId', - EVALUATING_AUDIENCE: '%s: Starting to evaluate audience "%s" with conditions: %s.', - EVALUATING_AUDIENCES_COMBINED: '%s: Evaluating audiences for %s "%s": %s.', - AUDIENCE_EVALUATION_RESULT: '%s: Audience "%s" evaluated to %s.', - AUDIENCE_EVALUATION_RESULT_COMBINED: '%s: Audiences for %s %s collectively evaluated to %s.', - MISSING_ATTRIBUTE_VALUE: - '%s: Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".', - UNEXPECTED_CONDITION_VALUE: - '%s: Audience condition %s evaluated to UNKNOWN because the condition value is not supported.', - UNEXPECTED_TYPE: - '%s: Audience condition %s evaluated to UNKNOWN because a value of type "%s" was passed for user attribute "%s".', - UNEXPECTED_TYPE_NULL: - '%s: Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".', - UNKNOWN_CONDITION_TYPE: - '%s: Audience condition %s has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.', - UNKNOWN_MATCH_TYPE: - '%s: Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.', - UPDATED_OPTIMIZELY_CONFIG: '%s: Updated Optimizely config to revision %s (project id %s)', - OUT_OF_BOUNDS: - '%s: Audience condition %s evaluated to UNKNOWN because the number value for user attribute "%s" is not in the range [-2^53, +2^53].', - UNABLE_TO_ATTACH_UNLOAD: '%s: unable to bind optimizely.close() to page unload event: "%s"', -}; export const enum RESERVED_EVENT_KEYWORDS { REVENUE = 'revenue', @@ -223,7 +44,7 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_CLIENT_ENGINE = 'react-sdk'; export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION ='5.3.4' +export const CLIENT_VERSION = '5.3.4'; export const DECISION_NOTIFICATION_TYPES = { AB_TEST: 'ab-test', diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index 9836afa14..fab537adb 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -13,12 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { + FAILED_TO_PARSE_REVENUE, + FAILED_TO_PARSE_VALUE, + PARSED_NUMERIC_VALUE, + PARSED_REVENUE_VALUE, +} from '../../log_messages'; import { EventTags } from '../../event_processor/event_builder/user_event'; import { LoggerFacade } from '../../modules/logging'; import { LOG_LEVEL, - LOG_MESSAGES, RESERVED_EVENT_KEYWORDS, } from '../enums'; @@ -45,10 +50,10 @@ export function getRevenueValue(eventTags: EventTags, logger: LoggerFacade): num const parsedRevenueValue = typeof rawValue === 'string' ? parseInt(rawValue) : rawValue; if (isFinite(parsedRevenueValue)) { - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); + logger.log(LOG_LEVEL.INFO, PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); return parsedRevenueValue; } else { // NaN, +/- infinity values - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_REVENUE, MODULE_NAME, rawValue); + logger.log(LOG_LEVEL.INFO, FAILED_TO_PARSE_REVENUE, MODULE_NAME, rawValue); return null; } } @@ -69,10 +74,10 @@ export function getEventValue(eventTags: EventTags, logger: LoggerFacade): numbe const parsedEventValue = typeof rawValue === 'string' ? parseFloat(rawValue) : rawValue; if (isFinite(parsedEventValue)) { - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_NUMERIC_VALUE, MODULE_NAME, parsedEventValue); + logger.log(LOG_LEVEL.INFO, PARSED_NUMERIC_VALUE, MODULE_NAME, parsedEventValue); return parsedEventValue; } else { // NaN, +/- infinity values - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_VALUE, MODULE_NAME, rawValue); + logger.log(LOG_LEVEL.INFO, FAILED_TO_PARSE_VALUE, MODULE_NAME, rawValue); return null; } -} \ No newline at end of file +} diff --git a/lib/utils/event_tags_validator/index.tests.js b/lib/utils/event_tags_validator/index.tests.js index 45dc75123..fcf8d4bd3 100644 --- a/lib/utils/event_tags_validator/index.tests.js +++ b/lib/utils/event_tags_validator/index.tests.js @@ -17,7 +17,7 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import { validate } from './'; -import { ERROR_MESSAGES } from'../enums'; +import { INVALID_EVENT_TAGS } from '../../error_messages'; describe('lib/utils/event_tags_validator', function() { describe('APIs', function() { @@ -30,13 +30,13 @@ describe('lib/utils/event_tags_validator', function() { var eventTagsArray = ['notGonnaWork']; assert.throws(function() { validate(eventTagsArray); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); }); it('should throw an error if event tags is null', function() { assert.throws(function() { validate(null); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); }); it('should throw an error if event tags is a function', function() { @@ -45,7 +45,7 @@ describe('lib/utils/event_tags_validator', function() { } assert.throws(function() { validate(invalidInput); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); }); }); }); diff --git a/lib/utils/event_tags_validator/index.ts b/lib/utils/event_tags_validator/index.ts index f2294dda0..d898cc202 100644 --- a/lib/utils/event_tags_validator/index.ts +++ b/lib/utils/event_tags_validator/index.ts @@ -17,10 +17,9 @@ /** * Provides utility method for validating that event tags user has provided are valid */ +import { INVALID_EVENT_TAGS } from '../../error_messages'; import { sprintf } from '../../utils/fns'; -import { ERROR_MESSAGES } from '../enums'; - const MODULE_NAME = 'EVENT_TAGS_VALIDATOR'; /** @@ -33,6 +32,6 @@ export function validate(eventTags: unknown): boolean { if (typeof eventTags === 'object' && !Array.isArray(eventTags) && eventTags !== null) { return true; } else { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, MODULE_NAME)); + throw new Error(sprintf(INVALID_EVENT_TAGS, MODULE_NAME)); } } diff --git a/lib/utils/executor/backoff_retry_runner.ts b/lib/utils/executor/backoff_retry_runner.ts index 504412c24..f939f9cc6 100644 --- a/lib/utils/executor/backoff_retry_runner.ts +++ b/lib/utils/executor/backoff_retry_runner.ts @@ -1,3 +1,4 @@ +import { RETRY_CANCELLED } from "../../exception_messages"; import { resolvablePromise, ResolvablePromise } from "../promise/resolvablePromise"; import { BackoffController } from "../repeater/repeater"; import { AsyncProducer, Fn } from "../type"; @@ -26,7 +27,7 @@ const runTask = <T>( return; } if (cancelSignal.cancelled) { - returnPromise.reject(new Error('Retry cancelled')); + returnPromise.reject(new Error(RETRY_CANCELLED)); return; } const delay = backoff?.backoff() ?? 0; diff --git a/lib/utils/http_request_handler/request_handler.browser.ts b/lib/utils/http_request_handler/request_handler.browser.ts index a2756e318..26e22425d 100644 --- a/lib/utils/http_request_handler/request_handler.browser.ts +++ b/lib/utils/http_request_handler/request_handler.browser.ts @@ -17,6 +17,8 @@ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import { LogHandler, LogLevel } from '../../modules/logging'; import { REQUEST_TIMEOUT_MS } from '../enums'; +import { REQUEST_ERROR, REQUEST_TIMEOUT } from '../../exception_messages'; +import { UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from '../../log_messages'; /** * Handles sending requests and receiving responses over HTTP via XMLHttpRequest @@ -50,7 +52,7 @@ export class BrowserRequestHandler implements RequestHandler { if (request.readyState === XMLHttpRequest.DONE) { const statusCode = request.status; if (statusCode === 0) { - reject(new Error('Request error')); + reject(new Error(REQUEST_ERROR)); return; } @@ -67,7 +69,7 @@ export class BrowserRequestHandler implements RequestHandler { request.timeout = this.timeout; request.ontimeout = (): void => { - this.logger?.log(LogLevel.WARNING, 'Request timed out'); + this.logger?.log(LogLevel.WARNING, REQUEST_TIMEOUT); }; request.send(data); @@ -122,7 +124,7 @@ export class BrowserRequestHandler implements RequestHandler { } } } catch { - this.logger?.log(LogLevel.WARNING, `Unable to parse & skipped header item '${headerLine}'`); + this.logger?.log(LogLevel.WARNING, UNABLE_TO_PARSE_AND_SKIPPED_HEADER, headerLine); } }); return headers; diff --git a/lib/utils/http_request_handler/request_handler.node.ts b/lib/utils/http_request_handler/request_handler.node.ts index 26bc6cbda..0530553b4 100644 --- a/lib/utils/http_request_handler/request_handler.node.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -20,6 +20,8 @@ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import decompressResponse from 'decompress-response'; import { LogHandler } from '../../modules/logging'; import { REQUEST_TIMEOUT_MS } from '../enums'; +import { sprintf } from '../fns'; +import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from '../../exception_messages'; /** * Handles sending requests and receiving responses over HTTP via NodeJS http module @@ -28,7 +30,7 @@ export class NodeRequestHandler implements RequestHandler { private readonly logger?: LogHandler; private readonly timeout: number; - constructor(opt: { logger?: LogHandler, timeout?: number } = {}) { + constructor(opt: { logger?: LogHandler; timeout?: number } = {}) { this.logger = opt.logger; this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS; } @@ -46,7 +48,7 @@ export class NodeRequestHandler implements RequestHandler { if (parsedUrl.protocol !== 'https:') { return { - responsePromise: Promise.reject(new Error(`Unsupported protocol: ${parsedUrl.protocol}`)), + responsePromise: Promise.reject(new Error(sprintf(UNSUPPORTED_PROTOCOL, parsedUrl.protocol))), abort: () => {}, }; } @@ -128,7 +130,7 @@ export class NodeRequestHandler implements RequestHandler { request.on('timeout', () => { aborted = true; request.destroy(); - reject(new Error('Request timed out')); + reject(new Error(REQUEST_TIMEOUT)); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -138,7 +140,7 @@ export class NodeRequestHandler implements RequestHandler { } else if (typeof err === 'string') { reject(new Error(err)); } else { - reject(new Error('Request error')); + reject(new Error(REQUEST_ERROR)); } }); @@ -164,7 +166,7 @@ export class NodeRequestHandler implements RequestHandler { } if (!incomingMessage.statusCode) { - reject(new Error('No status code in response')); + reject(new Error(NO_STATUS_CODE_IN_RESPONSE)); return; } diff --git a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts index 78deb7f2d..4a2fb77ed 100644 --- a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts +++ b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts @@ -16,11 +16,13 @@ import type { AsyncStorageStatic } from '@react-native-async-storage/async-storage' +export const MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE = 'Module not found: @react-native-async-storage/async-storage'; + export const getDefaultAsyncStorage = (): AsyncStorageStatic => { try { // eslint-disable-next-line @typescript-eslint/no-var-requires return require('@react-native-async-storage/async-storage').default; } catch (e) { - throw new Error('Module not found: @react-native-async-storage/async-storage'); + throw new Error(MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE); } }; diff --git a/lib/utils/json_schema_validator/index.tests.js b/lib/utils/json_schema_validator/index.tests.js index 61df2abaa..d54bc39a4 100644 --- a/lib/utils/json_schema_validator/index.tests.js +++ b/lib/utils/json_schema_validator/index.tests.js @@ -17,8 +17,8 @@ import { sprintf } from '../fns'; import { assert } from 'chai'; import { validate } from './'; -import { ERROR_MESSAGES } from '../enums'; import testData from '../../tests/test_data'; +import { NO_JSON_PROVIDED } from '../../error_messages'; describe('lib/utils/json_schema_validator', function() { @@ -33,7 +33,7 @@ describe('lib/utils/json_schema_validator', function() { it('should throw an error if no json object is passed in', function() { assert.throws(function() { validate(); - }, sprintf(ERROR_MESSAGES.NO_JSON_PROVIDED, 'JSON_SCHEMA_VALIDATOR (Project Config JSON Schema)')); + }, sprintf(NO_JSON_PROVIDED, 'JSON_SCHEMA_VALIDATOR (Project Config JSON Schema)')); }); it('should validate specified Optimizely datafile', function() { diff --git a/lib/utils/json_schema_validator/index.ts b/lib/utils/json_schema_validator/index.ts index 7ad8708c9..a4bac5674 100644 --- a/lib/utils/json_schema_validator/index.ts +++ b/lib/utils/json_schema_validator/index.ts @@ -16,8 +16,8 @@ import { sprintf } from '../fns'; import { JSONSchema4, validate as jsonSchemaValidator } from 'json-schema'; -import { ERROR_MESSAGES } from '../enums'; import schema from '../../project_config/project_config_schema'; +import { INVALID_DATAFILE, INVALID_JSON, NO_JSON_PROVIDED } from '../../error_messages'; const MODULE_NAME = 'JSON_SCHEMA_VALIDATOR'; @@ -36,7 +36,7 @@ export function validate( const moduleTitle = `${MODULE_NAME} (${validationSchema.title})`; if (typeof jsonObject !== 'object' || jsonObject === null) { - throw new Error(sprintf(ERROR_MESSAGES.NO_JSON_PROVIDED, moduleTitle)); + throw new Error(sprintf(NO_JSON_PROVIDED, moduleTitle)); } const result = jsonSchemaValidator(jsonObject, validationSchema); @@ -50,9 +50,9 @@ export function validate( if (Array.isArray(result.errors)) { throw new Error( - sprintf(ERROR_MESSAGES.INVALID_DATAFILE, moduleTitle, result.errors[0].property, result.errors[0].message) + sprintf(INVALID_DATAFILE, moduleTitle, result.errors[0].property, result.errors[0].message) ); } - throw new Error(sprintf(ERROR_MESSAGES.INVALID_JSON, moduleTitle)); + throw new Error(sprintf(INVALID_JSON, moduleTitle)); } diff --git a/lib/utils/semantic_version/index.ts b/lib/utils/semantic_version/index.ts index e8ef11c7f..cdc479bf0 100644 --- a/lib/utils/semantic_version/index.ts +++ b/lib/utils/semantic_version/index.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; import { getLogger } from '../../modules/logging'; -import { VERSION_TYPE, LOG_MESSAGES } from '../enums'; +import { VERSION_TYPE } from '../enums'; const MODULE_NAME = 'SEMANTIC VERSION'; const logger = getLogger(); @@ -93,7 +94,7 @@ function splitVersion(version: string): string[] | null { // check that version shouldn't have white space if (hasWhiteSpaces(version)) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); return null; } //check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release @@ -113,18 +114,18 @@ function splitVersion(version: string): string[] | null { const dotCount = targetPrefix.split('.').length - 1; if (dotCount > 2) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); return null; } const targetVersionParts = targetPrefix.split('.'); if (targetVersionParts.length != dotCount + 1) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); return null; } for (const part of targetVersionParts) { if (!isNumber(part)) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); return null; } } diff --git a/lib/utils/user_profile_service_validator/index.tests.js b/lib/utils/user_profile_service_validator/index.tests.js index 1237a0894..f12f790ea 100644 --- a/lib/utils/user_profile_service_validator/index.tests.js +++ b/lib/utils/user_profile_service_validator/index.tests.js @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ - import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import { validate } from './'; -import { ERROR_MESSAGES } from '../enums'; +import { INVALID_USER_PROFILE_SERVICE } from '../../error_messages'; describe('lib/utils/user_profile_service_validator', function() { describe('APIs', function() { @@ -30,7 +29,7 @@ describe('lib/utils/user_profile_service_validator', function() { assert.throws(function() { validate(missingLookupFunction); }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, + INVALID_USER_PROFILE_SERVICE, 'USER_PROFILE_SERVICE_VALIDATOR', "Missing function 'lookup'" )); @@ -44,7 +43,7 @@ describe('lib/utils/user_profile_service_validator', function() { assert.throws(function() { validate(lookupNotFunction); }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, + INVALID_USER_PROFILE_SERVICE, 'USER_PROFILE_SERVICE_VALIDATOR', "Missing function 'lookup'" )); @@ -57,7 +56,7 @@ describe('lib/utils/user_profile_service_validator', function() { assert.throws(function() { validate(missingSaveFunction); }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, + INVALID_USER_PROFILE_SERVICE, 'USER_PROFILE_SERVICE_VALIDATOR', "Missing function 'save'" )); @@ -71,7 +70,7 @@ describe('lib/utils/user_profile_service_validator', function() { assert.throws(function() { validate(saveNotFunction); }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, + INVALID_USER_PROFILE_SERVICE, 'USER_PROFILE_SERVICE_VALIDATOR', "Missing function 'save'" )); diff --git a/lib/utils/user_profile_service_validator/index.ts b/lib/utils/user_profile_service_validator/index.ts index 57df0c891..8f51fc137 100644 --- a/lib/utils/user_profile_service_validator/index.ts +++ b/lib/utils/user_profile_service_validator/index.ts @@ -20,8 +20,7 @@ import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; - -import { ERROR_MESSAGES } from '../enums'; +import { INVALID_USER_PROFILE_SERVICE } from '../../error_messages'; const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; @@ -35,11 +34,11 @@ const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; export function validate(userProfileServiceInstance: unknown): boolean { if (typeof userProfileServiceInstance === 'object' && userProfileServiceInstance !== null) { if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['lookup'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'lookup'")); + throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'lookup'")); } else if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['save'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'save'")); + throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'save'")); } return true; } - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, MODULE_NAME)); + throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME)); } diff --git a/lib/vuid/vuid_manager_factory.node.spec.ts b/lib/vuid/vuid_manager_factory.node.spec.ts index 2a81f9a8a..048704794 100644 --- a/lib/vuid/vuid_manager_factory.node.spec.ts +++ b/lib/vuid/vuid_manager_factory.node.spec.ts @@ -17,10 +17,11 @@ import { vi, describe, expect, it } from 'vitest'; import { createVuidManager } from './vuid_manager_factory.node'; +import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from '../exception_messages'; describe('createVuidManager', () => { it('should throw an error', () => { expect(() => createVuidManager({ enableVuid: true })) - .toThrowError('VUID is not supported in Node.js environment'); + .toThrowError(VUID_IS_NOT_SUPPORTED_IN_NODEJS); }); }); diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts index 993fbb60a..6d194ce0b 100644 --- a/lib/vuid/vuid_manager_factory.node.ts +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from '../exception_messages'; import { VuidManager } from './vuid_manager'; import { VuidManagerOptions } from './vuid_manager_factory'; export const createVuidManager = (options: VuidManagerOptions): VuidManager => { - throw new Error('VUID is not supported in Node.js environment'); + throw new Error(VUID_IS_NOT_SUPPORTED_IN_NODEJS); }; diff --git a/message_generator.ts b/message_generator.ts index 2c7cd53b1..d4b03fb04 100644 --- a/message_generator.ts +++ b/message_generator.ts @@ -25,7 +25,7 @@ const generate = async () => { genOut += `export const messages = ${JSON.stringify(messages, null, 2)};` await writeFile(genFilePath, genOut, 'utf-8'); - }; + } } generate().then(() => { diff --git a/package.json b/package.json index dbb2bf109..b4f553c4b 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "prepare": "npm run build", "prepublishOnly": "npm test && npm run test-ci", "postbuild:win": "@powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.min.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.min.d.ts\"", - "genmsg": "jiti message_generator ./lib/error_messages.ts" + "genmsg": "jiti message_generator ./lib/error_messages.ts ./lib/log_messages.ts ./lib/exception_messages.ts" }, "repository": { "type": "git", From 92ab2a7ba8902b2691b118f6d8b2e5b7d6e729c5 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:28:18 +0600 Subject: [PATCH 109/200] [FSSDK-11003] disposable service implementation (#981) --- .../batch_event_processor.spec.ts | 73 ++++++++++++++++--- lib/event_processor/batch_event_processor.ts | 39 +++++++--- .../event_processor_factory.spec.ts | 4 +- .../event_processor_factory.ts | 4 +- lib/exception_messages.ts | 1 - .../event_manager/odp_event_manager.spec.ts | 34 +++++++++ lib/odp/event_manager/odp_event_manager.ts | 7 ++ lib/odp/odp_manager.spec.ts | 15 ++++ lib/odp/odp_manager.ts | 5 ++ lib/optimizely/index.spec.ts | 28 +++---- lib/optimizely/index.ts | 18 +++-- .../polling_datafile_manager.spec.ts | 43 +++++++++++ .../polling_datafile_manager.ts | 8 +- .../project_config_manager.spec.ts | 30 +++----- lib/project_config/project_config_manager.ts | 33 ++------- lib/service.ts | 7 +- lib/shared_types.ts | 4 +- lib/tests/mock/mock_project_config_manager.ts | 8 +- lib/tests/mock/mock_repeater.ts | 2 +- 19 files changed, 261 insertions(+), 102 deletions(-) diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index 30d8d1bac..4e955e364 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -94,7 +94,7 @@ describe('QueueingEventProcessor', async () => { await expect(processor.onRunning()).resolves.not.toThrow(); }); - it('should start dispatchRepeater and failedEventRepeater', () => { + it('should start failedEventRepeater', () => { const eventDispatcher = getMockDispatcher(); const dispatchRepeater = getMockRepeater(); const failedEventRepeater = getMockRepeater(); @@ -107,7 +107,6 @@ describe('QueueingEventProcessor', async () => { }); processor.start(); - expect(dispatchRepeater.start).toHaveBeenCalledOnce(); expect(failedEventRepeater.start).toHaveBeenCalledOnce(); }); @@ -167,7 +166,7 @@ describe('QueueingEventProcessor', async () => { processor.start(); await processor.onRunning(); - for(let i = 0; i < 100; i++) { + for(let i = 0; i < 99; i++) { const event = createImpressionEvent(`id-${i}`); await processor.process(event); } @@ -175,6 +174,25 @@ describe('QueueingEventProcessor', async () => { expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); }); + it('should start the dispatchRepeater if it is not running', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const event = createImpressionEvent('id-1'); + await processor.process(event); + + expect(dispatchRepeater.start).toHaveBeenCalledOnce(); + }); + it('should dispatch events if queue is full and clear queue', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; @@ -190,7 +208,7 @@ describe('QueueingEventProcessor', async () => { await processor.onRunning(); let events: ProcessableEvent[] = []; - for(let i = 0; i < 100; i++) { + for(let i = 0; i < 99; i++){ const event = createImpressionEvent(`id-${i}`); events.push(event); await processor.process(event); @@ -198,14 +216,16 @@ describe('QueueingEventProcessor', async () => { expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); - let event = createImpressionEvent('id-100'); + let event = createImpressionEvent('id-99'); + events.push(event); await processor.process(event); - + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); - events = [event]; - for(let i = 101; i < 200; i++) { + events = []; + + for(let i = 100; i < 199; i++) { const event = createImpressionEvent(`id-${i}`); events.push(event); await processor.process(event); @@ -213,7 +233,8 @@ describe('QueueingEventProcessor', async () => { expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - event = createImpressionEvent('id-200'); + event = createImpressionEvent('id-199'); + events.push(event); await processor.process(event); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); @@ -257,6 +278,40 @@ describe('QueueingEventProcessor', async () => { expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(buildLogEvent([newEvent])); }); + it('should flush queue immediately regardless of batchSize, if event processor is disposable', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.makeDisposable(); + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + const event = createImpressionEvent('id-1'); + events.push(event); + await processor.process(event); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); + expect(dispatchRepeater.reset).toHaveBeenCalledTimes(1); + expect(dispatchRepeater.start).not.toHaveBeenCalled(); + expect(failedEventRepeater.start).not.toHaveBeenCalled(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(processor.retryConfig?.maxRetries).toEqual(5); + }); + it('should store the event in the eventStore with increasing ids', async () => { const eventDispatcher = getMockDispatcher(); const eventStore = getMockSyncCache<EventWithId>(); diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 76e737a9d..a487f6cdf 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -30,6 +30,9 @@ import { areEventContextsEqual } from "./event_builder/user_event"; import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "../exception_messages"; import { sprintf } from "../utils/fns"; +export const DEFAULT_MIN_BACKOFF = 1000; +export const DEFAULT_MAX_BACKOFF = 32000; + export type EventWithId = { id: string; event: ProcessableEvent; @@ -209,7 +212,8 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { if (!batch) { return; } - + + this.dispatchRepeater.reset(); this.dispatchBatch(batch, closing); } @@ -218,10 +222,6 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { return Promise.reject('Event processor is not running'); } - if (this.eventQueue.length == this.batchSize) { - this.flush(); - } - const eventWithId = { id: this.idGenerator.getId(), event: event, @@ -232,29 +232,50 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { if (this.eventQueue.length > 0 && !areEventContextsEqual(this.eventQueue[0].event, event)) { this.flush(); } - this.eventQueue.push(eventWithId); + + this.eventQueue.push(eventWithId); + + if (this.eventQueue.length == this.batchSize) { + this.flush(); + } else if (!this.dispatchRepeater.isRunning()) { + this.dispatchRepeater.start(); + } + } start(): void { if (!this.isNew()) { return; } + super.start(); this.state = ServiceState.Running; - this.dispatchRepeater.start(); - this.failedEventRepeater?.start(); + + if(!this.disposable) { + this.failedEventRepeater?.start(); + } this.retryFailedEvents(); this.startPromise.resolve(); } + makeDisposable(): void { + super.makeDisposable(); + this.batchSize = 1; + this.retryConfig = { + maxRetries: Math.min(this.retryConfig?.maxRetries ?? 5, 5), + backoffProvider: + this.retryConfig?.backoffProvider || + (() => new ExponentialBackoff(DEFAULT_MIN_BACKOFF, DEFAULT_MAX_BACKOFF, 500)), + } + } + stop(): void { if (this.isDone()) { return; } if (this.isNew()) { - // TOOD: replace message with imported constants this.startPromise.reject(new Error(EVENT_PROCESSOR_STOPPED)); } diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts index 2f3d45408..938483f4f 100644 --- a/lib/event_processor/event_processor_factory.spec.ts +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -15,8 +15,8 @@ */ import { describe, it, expect, beforeEach, vi, MockInstance } from 'vitest'; -import { DEFAULT_EVENT_BATCH_SIZE, DEFAULT_EVENT_FLUSH_INTERVAL, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, getBatchEventProcessor } from './event_processor_factory'; -import { BatchEventProcessor, BatchEventProcessorConfig, EventWithId } from './batch_event_processor'; +import { DEFAULT_EVENT_BATCH_SIZE, DEFAULT_EVENT_FLUSH_INTERVAL, getBatchEventProcessor } from './event_processor_factory'; +import { BatchEventProcessor, BatchEventProcessorConfig, EventWithId,DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF } from './batch_event_processor'; import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; import { getMockSyncCache } from '../tests/mock/mock_cache'; import { LogLevel } from '../modules/logging'; diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index 8221e7dab..f64143cf8 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -19,14 +19,12 @@ import { StartupLog } from "../service"; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; import { EventDispatcher } from "./event_dispatcher/event_dispatcher"; import { EventProcessor } from "./event_processor"; -import { BatchEventProcessor, EventWithId, RetryConfig } from "./batch_event_processor"; +import { BatchEventProcessor, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, EventWithId, RetryConfig } from "./batch_event_processor"; import { AsyncPrefixCache, Cache, SyncPrefixCache } from "../utils/cache/cache"; export const DEFAULT_EVENT_BATCH_SIZE = 10; export const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; export const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; -export const DEFAULT_MIN_BACKOFF = 1000; -export const DEFAULT_MAX_BACKOFF = 32000; export const FAILED_EVENT_RETRY_INTERVAL = 20 * 1000; export const EVENT_STORE_PREFIX = 'optly_event:'; diff --git a/lib/exception_messages.ts b/lib/exception_messages.ts index f17fa2821..731607ff8 100644 --- a/lib/exception_messages.ts +++ b/lib/exception_messages.ts @@ -31,7 +31,6 @@ export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it coul export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; export const FAILED_TO_STOP = 'Failed to stop'; -export const YOU_MUST_PROVIDE_DATAFILE_IN_SSR = 'You must provide datafile in SSR'; export const YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE = 'You must provide at least one of sdkKey or datafile'; export const RETRY_CANCELLED = 'Retry cancelled'; export const REQUEST_TIMEOUT = 'Request timeout'; diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index 67b874509..ff7efa5cb 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -207,6 +207,40 @@ describe('DefaultOdpEventManager', () => { } }); + it('should flush the queue immediately if disposable, regardless of the batchSize', async () => { + const apiManager = getMockApiManager(); + const repeater = getMockRepeater() + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater, + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + odpEventManager.makeDisposable(); + odpEventManager.start(); + + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const event = makeEvent(0); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, [event]); + expect(repeater.reset).toHaveBeenCalledTimes(1); + expect(repeater.start).not.toHaveBeenCalled(); + }) + it('drops events and logs if the state is not running', async () => { const apiManager = getMockApiManager(); apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 6ebe5aaa0..76aec79be 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -107,6 +107,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } super.start(); + if (this.odpIntegrationConfig) { this.goToRunningState(); } else { @@ -114,6 +115,12 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } } + makeDisposable(): void { + super.makeDisposable(); + this.retryConfig.maxRetries = Math.min(this.retryConfig.maxRetries, 5); + this.batchSize = 1; + } + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void { if (this.isDone()) { return; diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index dc6e2b96b..8ffc2721d 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -51,6 +51,7 @@ const getMockOdpEventManager = () => { getState: vi.fn(), updateConfig: vi.fn(), sendEvent: vi.fn(), + makeDisposable: vi.fn(), }; }; @@ -696,5 +697,19 @@ describe('DefaultOdpManager', () => { eventManagerTerminatedPromise.reject(new Error(FAILED_TO_STOP)); await expect(odpManager.onTerminated()).rejects.toThrow(); }); + + it('should call makeDisposable() on eventManager when makeDisposable() is called on odpManager', async () => { + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.makeDisposable(); + + expect(eventManager.makeDisposable).toHaveBeenCalled(); + }) }); diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 05c476ff3..4029a3621 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -108,6 +108,11 @@ export class DefaultOdpManager extends BaseService implements OdpManager { }); } + makeDisposable(): void { + super.makeDisposable(); + this.eventManager.makeDisposable(); + } + private handleStartSuccess() { if (this.isDone()) { return; diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 5ced36a08..1825bb9a2 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -16,15 +16,13 @@ import { describe, it, expect, vi } from 'vitest'; import Optimizely from '.'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; -import * as logger from '../plugins/logger'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; -import { LOG_LEVEL } from '../common_exports'; import { createNotificationCenter } from '../notification_center'; import testData from '../tests/test_data'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; -import { LoggerFacade } from '../modules/logging'; import { createProjectConfig } from '../project_config/project_config'; import { getMockLogger } from '../tests/mock/mock_logger'; +import { createOdpManager } from '../odp/odp_manager_factory.node'; describe('Optimizely', () => { const errorHandler = { handleError: function() {} }; @@ -34,19 +32,20 @@ describe('Optimizely', () => { }; const eventProcessor = getForwardingEventProcessor(eventDispatcher); - + const odpManager = createOdpManager({}); const logger = getMockLogger(); - const notificationCenter = createNotificationCenter({ logger, errorHandler }); - it('should pass ssr to the project config manager', () => { + it('should pass disposable options to the respective services', () => { const projectConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), }); - vi.spyOn(projectConfigManager, 'setSsr'); + vi.spyOn(projectConfigManager, 'makeDisposable'); + vi.spyOn(eventProcessor, 'makeDisposable'); + vi.spyOn(odpManager, 'makeDisposable'); - const instance = new Optimizely({ + new Optimizely({ clientEngine: 'node-sdk', projectConfigManager, errorHandler, @@ -54,16 +53,13 @@ describe('Optimizely', () => { logger, notificationCenter, eventProcessor, - isSsr: true, + odpManager, + disposable: true, isValidInstance: true, }); - expect(projectConfigManager.setSsr).toHaveBeenCalledWith(true); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(instance.getProjectConfig()).toBe(projectConfigManager.config); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(projectConfigManager.isSsr).toBe(true); + expect(projectConfigManager.makeDisposable).toHaveBeenCalled(); + expect(eventProcessor.makeDisposable).toHaveBeenCalled(); + expect(odpManager.makeDisposable).toHaveBeenCalled(); }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 34fa116f6..d71abfd3a 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -141,10 +141,20 @@ export default class Optimizely implements Client { this.errorHandler = config.errorHandler; this.isOptimizelyConfigValid = config.isValidInstance; this.logger = config.logger; + this.projectConfigManager = config.projectConfigManager; + this.notificationCenter = config.notificationCenter; this.odpManager = config.odpManager; this.vuidManager = config.vuidManager; + this.eventProcessor = config.eventProcessor; + + if(config.disposable) { + this.projectConfigManager.makeDisposable(); + this.eventProcessor?.makeDisposable(); + this.odpManager?.makeDisposable(); + } let decideOptionsArray = config.defaultDecideOptions ?? []; + if (!Array.isArray(decideOptionsArray)) { this.logger.log(LOG_LEVEL.DEBUG, INVALID_DEFAULT_DECIDE_OPTIONS, MODULE_NAME); decideOptionsArray = []; @@ -160,7 +170,6 @@ export default class Optimizely implements Client { } }); this.defaultDecideOptions = defaultDecideOptions; - this.projectConfigManager = config.projectConfigManager; this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { this.logger.log( @@ -176,7 +185,6 @@ export default class Optimizely implements Client { this.updateOdpSettings(); }); - this.projectConfigManager.setSsr(config.isSsr) this.projectConfigManager.start(); const projectConfigManagerRunningPromise = this.projectConfigManager.onRunning(); @@ -198,10 +206,6 @@ export default class Optimizely implements Client { UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, }); - this.notificationCenter = config.notificationCenter; - - this.eventProcessor = config.eventProcessor; - this.eventProcessor?.start(); const eventProcessorRunningPromise = this.eventProcessor ? this.eventProcessor.onRunning() : Promise.resolve(undefined); @@ -210,6 +214,8 @@ export default class Optimizely implements Client { this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, event); }); + this.odpManager?.start(); + this.readyPromise = Promise.all([ projectConfigManagerRunningPromise, eventProcessorRunningPromise, diff --git a/lib/project_config/polling_datafile_manager.spec.ts b/lib/project_config/polling_datafile_manager.spec.ts index 3efae54d7..642061d96 100644 --- a/lib/project_config/polling_datafile_manager.spec.ts +++ b/lib/project_config/polling_datafile_manager.spec.ts @@ -265,6 +265,30 @@ describe('PollingDatafileManager', () => { await expect(manager.onTerminated()).rejects.toThrow(); }); + it('retries min(initRetry, 5) amount of times if datafile manager is disposable', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 10, + }); + manager.makeDisposable(); + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(i); + await expect(ret).rejects.toThrow(); + } + + expect(repeater.isRunning()).toBe(false); + expect(() => repeater.execute(6)).toThrow(); + }) + it('retries specified number of times before rejecting onRunning() and onTerminated() when autoupdate is false', async () => { const repeater = getMockRepeater(); const requestHandler = getMockRequestHandler(); @@ -470,6 +494,25 @@ describe('PollingDatafileManager', () => { expect(repeater.stop).toHaveBeenCalled(); }); + it('stops repeater after successful initialization if disposable is true', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + manager.makeDisposable(); + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + }); + it('saves the datafile in cache', async () => { const repeater = getMockRepeater(); const requestHandler = getMockRequestHandler(); diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index bde704029..62b17cfe4 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -96,6 +96,11 @@ export class PollingDatafileManager extends BaseService implements DatafileManag this.repeater.start(true); } + makeDisposable(): void { + super.makeDisposable(); + this.initRetryRemaining = Math.min(this.initRetryRemaining ?? 5, 5); + } + stop(): void { if (this.isDone()) { return; @@ -162,7 +167,8 @@ export class PollingDatafileManager extends BaseService implements DatafileManag if (datafile) { this.handleDatafile(datafile); // if autoUpdate is off, don't need to sync datafile any more - if (!this.autoUpdate) { + // if disposable, stop the repeater after the first successful fetch + if (!this.autoUpdate || this.disposable) { this.repeater.stop(); } } diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts index 7bed978ef..acd8538ee 100644 --- a/lib/project_config/project_config_manager.spec.ts +++ b/lib/project_config/project_config_manager.spec.ts @@ -165,17 +165,6 @@ describe('ProjectConfigManagerImpl', () => { await manager.onRunning(); expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); }); - - it('should not start datafileManager if isSsr is true and return correct config', () => { - const datafileManager = getMockDatafileManager({}); - vi.spyOn(datafileManager, 'start'); - const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); - manager.setSsr(true); - manager.start(); - - expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); - expect(datafileManager.start).not.toHaveBeenCalled(); - }); }); describe('when datafile is invalid', () => { @@ -409,16 +398,6 @@ describe('ProjectConfigManagerImpl', () => { expect(logger.error).toHaveBeenCalled(); }); - it('should reject onRunning() and log error if isSsr is true and datafile is not provided', async () =>{ - const logger = getMockLogger(); - const manager = new ProjectConfigManagerImpl({ logger, datafileManager: getMockDatafileManager({})}); - manager.setSsr(true); - manager.start(); - - await expect(manager.onRunning()).rejects.toThrow(); - expect(logger.error).toHaveBeenCalled(); - }); - it('should reject onRunning() and log error if the datafile version is not supported', async () => { const logger = getMockLogger(); const datafile = testData.getUnsupportedVersionConfig(); @@ -538,6 +517,15 @@ describe('ProjectConfigManagerImpl', () => { expect(listener).toHaveBeenCalledTimes(1); }); + + it('should make datafileManager disposable if makeDisposable() is called', async () => { + const datafileManager = getMockDatafileManager({}); + vi.spyOn(datafileManager, 'makeDisposable'); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.makeDisposable(); + + expect(datafileManager.makeDisposable).toHaveBeenCalled(); + }) }); }); }); diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index 81ee87b78..a0ebbffdb 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -26,13 +26,10 @@ import { DATAFILE_MANAGER_FAILED_TO_START, DATAFILE_MANAGER_STOPPED, YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE, - YOU_MUST_PROVIDE_DATAFILE_IN_SSR, } from '../exception_messages'; interface ProjectConfigManagerConfig { - // TODO: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object; + datafile?: string | Record<string, unknown>; jsonSchemaValidator?: Transformer<unknown, boolean>, datafileManager?: DatafileManager; logger?: LoggerFacade; @@ -40,7 +37,6 @@ interface ProjectConfigManagerConfig { export interface ProjectConfigManager extends Service { setLogger(logger: LoggerFacade): void; - setSsr(isSsr?: boolean): void; getConfig(): ProjectConfig | undefined; getOptimizelyConfig(): OptimizelyConfig | undefined; onUpdate(listener: Consumer<ProjectConfig>): Fn; @@ -60,7 +56,6 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf public jsonSchemaValidator?: Transformer<unknown, boolean>; public datafileManager?: DatafileManager; private eventEmitter: EventEmitter<{ update: ProjectConfig }> = new EventEmitter(); - private isSsr = false; constructor(config: ProjectConfigManagerConfig) { super(); @@ -77,17 +72,8 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf this.state = ServiceState.Starting; - if(this.isSsr) { - // If isSsr is true, we don't need to poll for datafile updates - this.datafileManager = undefined - } - if (!this.datafile && !this.datafileManager) { - const errorMessage = this.isSsr - ? YOU_MUST_PROVIDE_DATAFILE_IN_SSR - : YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE; - - this.handleInitError(new Error(errorMessage)); + this.handleInitError(new Error(YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE)); return; } @@ -110,6 +96,11 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf }); } + makeDisposable(): void { + super.makeDisposable(); + this.datafileManager?.makeDisposable(); + } + private handleInitError(error: Error): void { this.logger?.error(error); this.state = ServiceState.Failed; @@ -206,7 +197,6 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf } if (this.isNew() || this.isStarting()) { - // TOOD: replace message with imported constants this.startPromise.reject(new Error(DATAFILE_MANAGER_STOPPED)); } @@ -227,13 +217,4 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf this.stopPromise.reject(err); }); } - - /** - * Set the isSsr flag to indicate if the project config manager is being used in a server side rendering environment - * @param {Boolean} isSsr - * @returns {void} - */ - setSsr(isSsr: boolean): void { - this.isSsr = isSsr; - } } diff --git a/lib/service.ts b/lib/service.ts index 459488027..2d0877bee 100644 --- a/lib/service.ts +++ b/lib/service.ts @@ -51,6 +51,7 @@ export interface Service { // either by failing to start or stop. // It will resolve if the service is stopped successfully. onTerminated(): Promise<void>; + makeDisposable(): void; } export abstract class BaseService implements Service { @@ -59,7 +60,7 @@ export abstract class BaseService implements Service { protected stopPromise: ResolvablePromise<void>; protected logger?: LoggerFacade; protected startupLogs: StartupLog[]; - + protected disposable = false; constructor(startupLogs: StartupLog[] = []) { this.state = ServiceState.New; this.startPromise = resolvablePromise(); @@ -71,6 +72,10 @@ export abstract class BaseService implements Service { this.stopPromise.promise.catch(() => {}); } + makeDisposable(): void { + this.disposable = true; + } + setLogger(logger: LoggerFacade): void { this.logger = logger; } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index b2ebad540..299dc9332 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -263,10 +263,10 @@ export interface OptimizelyOptions { sdkKey?: string; userProfileService?: UserProfileService | null; defaultDecideOptions?: OptimizelyDecideOption[]; - isSsr?:boolean; odpManager?: OdpManager; notificationCenter: DefaultNotificationCenter; vuidManager?: VuidManager + disposable?: boolean; } /** @@ -384,9 +384,9 @@ export interface Config { defaultDecideOptions?: OptimizelyDecideOption[]; clientEngine?: string; clientVersion?: string; - isSsr?: boolean; odpManager?: OdpManager; vuidManager?: VuidManager; + disposable?: boolean; } export type OptimizelyExperimentsMap = { diff --git a/lib/tests/mock/mock_project_config_manager.ts b/lib/tests/mock/mock_project_config_manager.ts index b76f71e2d..65c6268ab 100644 --- a/lib/tests/mock/mock_project_config_manager.ts +++ b/lib/tests/mock/mock_project_config_manager.ts @@ -26,12 +26,12 @@ type MockOpt = { export const getMockProjectConfigManager = (opt: MockOpt = {}): ProjectConfigManager => { return { - isSsr: false, + disposable: false, config: opt.initConfig, - start: () => {}, - setSsr: function(isSsr:boolean) { - this.isSsr = isSsr; + makeDisposable(){ + this.disposable = true; }, + start: () => {}, onRunning: () => opt.onRunning || Promise.resolve(), stop: () => {}, onTerminated: () => opt.onTerminated || Promise.resolve(), diff --git a/lib/tests/mock/mock_repeater.ts b/lib/tests/mock/mock_repeater.ts index adf6baf83..f70b0b477 100644 --- a/lib/tests/mock/mock_repeater.ts +++ b/lib/tests/mock/mock_repeater.ts @@ -31,7 +31,7 @@ export const getMockRepeater = () => { // throw if not running. This ensures tests cannot // do mock exection when the repeater is supposed to be not running. execute(failureCount: number): Promise<void> { - if (!this.isRunning) throw new Error(); + if (!this.isRunning()) throw new Error(); const ret = this.handler?.(failureCount); ret?.catch(() => {}); return ret; From 2f02b6962cbe43533795feebda1af16567b999ee Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 14 Jan 2025 20:15:04 +0600 Subject: [PATCH 110/200] [FSSDK-11035] refactor logger and error handler (#982) --- lib/common_exports.ts | 2 - lib/core/audience_evaluator/index.tests.js | 138 +- lib/core/audience_evaluator/index.ts | 32 +- .../index.tests.js | 35 +- .../odp_segment_condition_evaluator/index.ts | 17 +- lib/core/bucketer/index.tests.js | 130 +- lib/core/bucketer/index.ts | 38 +- .../index.tests.js | 525 ++- .../index.ts | 140 +- lib/core/decision_service/index.tests.js | 463 ++- lib/core/decision_service/index.ts | 155 +- .../index.ts => error/error_handler.ts} | 16 +- lib/error/error_notifier.ts | 32 + lib/error/error_reporter.ts | 40 + lib/error/optimizly_error.ts | 32 + lib/error_messages.ts | 53 +- .../batch_event_processor.spec.ts | 14 +- lib/event_processor/batch_event_processor.ts | 2 +- .../event_builder/user_event.ts | 11 +- lib/event_processor/event_processor.ts | 1 - .../event_processor_factory.spec.ts | 10 +- .../event_processor_factory.ts | 6 +- lib/exception_messages.ts | 4 - lib/index.browser.tests.js | 283 +- lib/index.browser.ts | 53 +- lib/index.lite.tests.js | 80 - lib/index.lite.ts | 53 +- lib/index.node.tests.js | 58 +- lib/index.node.ts | 51 +- lib/index.react_native.spec.ts | 138 +- lib/index.react_native.ts | 45 +- lib/log_messages.ts | 113 +- lib/logging/logger.spec.ts | 389 +++ lib/logging/logger.ts | 143 + lib/logging/logger_factory.ts | 20 + lib/message/message_resolver.ts | 20 + lib/modules/logging/errorHandler.ts | 67 - lib/modules/logging/logger.spec.ts | 388 --- lib/modules/logging/logger.ts | 333 -- lib/modules/logging/models.ts | 42 - lib/notification_center/index.tests.js | 10 +- lib/notification_center/index.ts | 31 +- .../event_manager/odp_event_api_manager.ts | 2 +- lib/odp/odp_manager.ts | 2 +- .../odp_segment_api_manager.ts | 2 +- .../segment_manager/odp_segment_manager.ts | 2 +- lib/optimizely/index.spec.ts | 7 +- lib/optimizely/index.tests.js | 2906 +++++++---------- lib/optimizely/index.ts | 200 +- lib/optimizely_user_context/index.tests.js | 79 +- .../logger/index.react_native.tests.js | 146 +- lib/plugins/logger/index.react_native.ts | 112 +- lib/plugins/logger/index.tests.js | 224 +- lib/plugins/logger/index.ts | 34 - .../config_manager_factory.spec.ts | 4 +- lib/project_config/config_manager_factory.ts | 4 +- lib/project_config/datafile_manager.ts | 2 +- lib/project_config/optimizely_config.ts | 2 +- .../polling_datafile_manager.spec.ts | 10 +- lib/project_config/project_config.tests.js | 95 +- lib/project_config/project_config.ts | 47 +- lib/project_config/project_config_manager.ts | 2 +- lib/service.spec.ts | 13 +- lib/service.ts | 14 +- lib/shared_types.ts | 17 +- lib/tests/mock/mock_datafile_manager.ts | 2 +- lib/tests/mock/mock_logger.ts | 4 +- lib/utils/config_validator/index.ts | 2 +- lib/utils/event_tag_utils/index.tests.js | 32 +- lib/utils/event_tag_utils/index.ts | 14 +- lib/utils/fns/index.spec.ts | 18 +- lib/utils/fns/index.ts | 24 - .../request_handler.browser.spec.ts | 6 +- .../request_handler.browser.ts | 12 +- .../request_handler.node.spec.ts | 6 +- .../request_handler.node.ts | 8 +- lib/utils/semantic_version/index.ts | 19 +- lib/vuid/vuid_manager.ts | 2 +- message_generator.ts | 2 +- 79 files changed, 3380 insertions(+), 4910 deletions(-) rename lib/{modules/logging/index.ts => error/error_handler.ts} (71%) create mode 100644 lib/error/error_notifier.ts create mode 100644 lib/error/error_reporter.ts create mode 100644 lib/error/optimizly_error.ts delete mode 100644 lib/index.lite.tests.js create mode 100644 lib/logging/logger.spec.ts create mode 100644 lib/logging/logger.ts create mode 100644 lib/logging/logger_factory.ts create mode 100644 lib/message/message_resolver.ts delete mode 100644 lib/modules/logging/errorHandler.ts delete mode 100644 lib/modules/logging/logger.spec.ts delete mode 100644 lib/modules/logging/logger.ts delete mode 100644 lib/modules/logging/models.ts delete mode 100644 lib/plugins/logger/index.ts diff --git a/lib/common_exports.ts b/lib/common_exports.ts index c2718e911..c043796df 100644 --- a/lib/common_exports.ts +++ b/lib/common_exports.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -export { LogLevel, LogHandler, getLogger, setLogHandler } from './modules/logging'; export { LOG_LEVEL } from './utils/enums'; -export { createLogger } from './plugins/logger'; export { createStaticProjectConfigManager } from './project_config/config_manager_factory'; export { PollingConfigManagerConfig } from './project_config/config_manager_factory'; diff --git a/lib/core/audience_evaluator/index.tests.js b/lib/core/audience_evaluator/index.tests.js index 6fb545f10..6ab30ca08 100644 --- a/lib/core/audience_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/index.tests.js @@ -16,14 +16,20 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; -import { getLogger } from '../../modules/logging'; import AudienceEvaluator, { createAudienceEvaluator } from './index'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from '../../log_messages'; +// import { getEvaluator } from '../custom_attribute_condition_evaluator'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); -var mockLogger = getLogger(); +var mockLogger = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, +} var getMockUserContext = (attributes, segments) => ({ getAttributes: () => ({ ... (attributes || {})}), @@ -82,11 +88,17 @@ describe('lib/core/audience_evaluator', function() { var audienceEvaluator; beforeEach(function() { - sinon.stub(mockLogger, 'log'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); }); afterEach(function() { - mockLogger.log.restore(); + mockLogger.info.restore(); + mockLogger.debug.restore(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); describe('APIs', function() { @@ -170,7 +182,6 @@ describe('lib/core/audience_evaluator', function() { beforeEach(function() { sandbox.stub(conditionTreeEvaluator, 'evaluate'); - sandbox.stub(customAttributeConditionEvaluator, 'evaluate'); }); afterEach(function() { @@ -199,26 +210,40 @@ describe('lib/core/audience_evaluator', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(false); + + const mockCustomAttributeConditionEvaluator = sinon.stub().returns(false); + + sinon.stub(customAttributeConditionEvaluator, 'getEvaluator').returns({ + evaluate: mockCustomAttributeConditionEvaluator, + }); + + const audienceEvaluator = createAudienceEvaluator(); + var userAttributes = { device_model: 'android' }; var user = getMockUserContext(userAttributes); var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user, ); assert.isFalse(result); + + customAttributeConditionEvaluator.getEvaluator.restore(); }); }); describe('Audience evaluation logging', function() { var sandbox = sinon.sandbox.create(); + var mockCustomAttributeConditionEvaluator; beforeEach(function() { + mockCustomAttributeConditionEvaluator = sinon.stub(); sandbox.stub(conditionTreeEvaluator, 'evaluate'); - sandbox.stub(customAttributeConditionEvaluator, 'evaluate'); + sandbox.stub(customAttributeConditionEvaluator, 'getEvaluator').returns({ + evaluate: mockCustomAttributeConditionEvaluator, + }); }); afterEach(function() { @@ -229,69 +254,110 @@ describe('lib/core/audience_evaluator', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(null); + + mockCustomAttributeConditionEvaluator.returns(null); var userAttributes = { device_model: 5.5 }; var user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user ); assert.isFalse(result); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].' - ); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[1]), 'AUDIENCE_EVALUATOR: Audience "1" evaluated to UNKNOWN.'); + assert.strictEqual(2, mockLogger.debug.callCount); + + sinon.assert.calledWithExactly( + mockLogger.debug, + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ) + + sinon.assert.calledWithExactly( + mockLogger.debug, + AUDIENCE_EVALUATION_RESULT, + '1', + 'UNKNOWN' + ) }); it('logs correctly when conditionTreeEvaluator.evaluate returns true', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(true); + + mockCustomAttributeConditionEvaluator.returns(true); + var userAttributes = { device_model: 'iphone' }; var user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user, ); assert.isTrue(result); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].' - ); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[1]), 'AUDIENCE_EVALUATOR: Audience "1" evaluated to TRUE.'); + assert.strictEqual(2, mockLogger.debug.callCount); + sinon.assert.calledWithExactly( + mockLogger.debug, + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ) + + sinon.assert.calledWithExactly( + mockLogger.debug, + AUDIENCE_EVALUATION_RESULT, + '1', + 'TRUE' + ) }); it('logs correctly when conditionTreeEvaluator.evaluate returns false', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(false); + + mockCustomAttributeConditionEvaluator.returns(false); + var userAttributes = { device_model: 'android' }; var user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user, ); assert.isFalse(result); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].' - ); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[1]), 'AUDIENCE_EVALUATOR: Audience "1" evaluated to FALSE.'); + assert.strictEqual(2, mockLogger.debug.callCount); + + sinon.assert.calledWithExactly( + mockLogger.debug, + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ) + + sinon.assert.calledWithExactly( + mockLogger.debug, + AUDIENCE_EVALUATION_RESULT, + '1', + 'FALSE' + ) }); }); }); diff --git a/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts index 5bb9f5a15..e110ab569 100644 --- a/lib/core/audience_evaluator/index.ts +++ b/lib/core/audience_evaluator/index.ts @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../../modules/logging'; - -import fns from '../../utils/fns'; import { LOG_LEVEL, } from '../../utils/enums'; @@ -25,11 +22,13 @@ import * as odpSegmentsConditionEvaluator from './odp_segment_condition_evaluato import { Audience, Condition, OptimizelyUserContext } from '../../shared_types'; import { CONDITION_EVALUATOR_ERROR, UNKNOWN_CONDITION_TYPE } from '../../error_messages'; import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE} from '../../log_messages'; +import { LoggerFacade } from '../../logging/logger'; -const logger = getLogger(); const MODULE_NAME = 'AUDIENCE_EVALUATOR'; export class AudienceEvaluator { + private logger?: LoggerFacade; + private typeToEvaluatorMap: { [key: string]: { [key: string]: (condition: Condition, user: OptimizelyUserContext) => boolean | null @@ -43,11 +42,12 @@ export class AudienceEvaluator { * Optimizely evaluators cannot be overridden. * @constructor */ - constructor(UNSTABLE_conditionEvaluators: unknown) { + constructor(UNSTABLE_conditionEvaluators: unknown, logger?: LoggerFacade) { + this.logger = logger; this.typeToEvaluatorMap = { ...UNSTABLE_conditionEvaluators as any, - custom_attribute: customAttributeConditionEvaluator, - third_party_dimension: odpSegmentsConditionEvaluator, + custom_attribute: customAttributeConditionEvaluator.getEvaluator(this.logger), + third_party_dimension: odpSegmentsConditionEvaluator.getEvaluator(this.logger), }; } @@ -77,16 +77,15 @@ export class AudienceEvaluator { const evaluateAudience = (audienceId: string) => { const audience = audiencesById[audienceId]; if (audience) { - logger.log( - LOG_LEVEL.DEBUG, - EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions) + this.logger?.debug( + EVALUATING_AUDIENCE, audienceId, JSON.stringify(audience.conditions) ); const result = conditionTreeEvaluator.evaluate( audience.conditions as unknown[] , this.evaluateConditionWithUserAttributes.bind(this, user) ); const resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); - logger.log(LOG_LEVEL.DEBUG, AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText); + this.logger?.debug(AUDIENCE_EVALUATION_RESULT, audienceId, resultText); return result; } return null; @@ -105,15 +104,14 @@ export class AudienceEvaluator { evaluateConditionWithUserAttributes(user: OptimizelyUserContext, condition: Condition): boolean | null { const evaluator = this.typeToEvaluatorMap[condition.type]; if (!evaluator) { - logger.log(LOG_LEVEL.WARNING, UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition)); + this.logger?.warn(UNKNOWN_CONDITION_TYPE, JSON.stringify(condition)); return null; } try { return evaluator.evaluate(condition, user); } catch (err: any) { - logger.log( - LOG_LEVEL.ERROR, - CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message + this.logger?.error( + CONDITION_EVALUATOR_ERROR, condition.type, err.message ); } @@ -123,6 +121,6 @@ export class AudienceEvaluator { export default AudienceEvaluator; -export const createAudienceEvaluator = function(UNSTABLE_conditionEvaluators: unknown): AudienceEvaluator { - return new AudienceEvaluator(UNSTABLE_conditionEvaluators); +export const createAudienceEvaluator = function(UNSTABLE_conditionEvaluators: unknown, logger?: LoggerFacade): AudienceEvaluator { + return new AudienceEvaluator(UNSTABLE_conditionEvaluators, logger); }; diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js index 768484b24..684e28258 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js @@ -18,7 +18,6 @@ import { assert } from 'chai'; import { sprintf } from '../../../utils/fns'; import { LOG_LEVEL } from '../../../utils/enums'; -import * as logging from '../../../modules/logging'; import * as odpSegmentEvalutor from './'; import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; @@ -34,27 +33,34 @@ var getMockUserContext = (attributes, segments) => ({ isQualifiedFor: segment => segments.indexOf(segment) > -1 }); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/audience_evaluator/odp_segment_condition_evaluator', function() { - var stubLogHandler; + const mockLogger = createLogger(); + const { evaluate } = odpSegmentEvalutor.getEvaluator(mockLogger); beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); }); afterEach(function() { - logging.resetLogger(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); it('should return true when segment qualifies and known match type is provided', () => { - assert.isTrue(odpSegmentEvalutor.evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-1']))); + assert.isTrue(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-1']))); }); it('should return false when segment does not qualify and known match type is provided', () => { - assert.isFalse(odpSegmentEvalutor.evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-2']))); + assert.isFalse(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-2']))); }) it('should return null when segment qualifies but unknown match type is provided', () => { @@ -62,10 +68,9 @@ describe('lib/core/audience_evaluator/odp_segment_condition_evaluator', function ... odpSegment1Condition, "match": 'unknown', }; - assert.isNull(odpSegmentEvalutor.evaluate(invalidOdpMatchCondition, getMockUserContext({}, ['odp-segment-1']))); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(UNKNOWN_MATCH_TYPE, 'ODP_SEGMENT_CONDITION_EVALUATOR', JSON.stringify(invalidOdpMatchCondition))); + assert.isNull(evaluate(invalidOdpMatchCondition, getMockUserContext({}, ['odp-segment-1']))); + sinon.assert.calledOnce(mockLogger.warn); + assert.strictEqual(mockLogger.warn.args[0][0], UNKNOWN_MATCH_TYPE); + assert.strictEqual(mockLogger.warn.args[0][1], JSON.stringify(invalidOdpMatchCondition)); }); }); diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts index 54d7b5d93..4984dce51 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts @@ -14,13 +14,11 @@ * limitations under the License. * ***************************************************************************/ import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; -import { getLogger } from '../../../modules/logging'; +import { LoggerFacade } from '../../../logging/logger'; import { Condition, OptimizelyUserContext } from '../../../shared_types'; const MODULE_NAME = 'ODP_SEGMENT_CONDITION_EVALUATOR'; -const logger = getLogger(); - const QUALIFIED_MATCH_TYPE = 'qualified'; const MATCH_TYPES = [ @@ -28,10 +26,19 @@ const MATCH_TYPES = [ ]; type ConditionEvaluator = (condition: Condition, user: OptimizelyUserContext) => boolean | null; +type Evaluator = { evaluate: (condition: Condition, user: OptimizelyUserContext) => boolean | null; } const EVALUATORS_BY_MATCH_TYPE: { [conditionType: string]: ConditionEvaluator | undefined } = {}; EVALUATORS_BY_MATCH_TYPE[QUALIFIED_MATCH_TYPE] = qualifiedEvaluator; +export const getEvaluator = (logger?: LoggerFacade): Evaluator => { + return { + evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { + return evaluate(condition, user, logger); + } + }; +} + /** * Given a custom attribute audience condition and user attributes, evaluate the * condition against the attributes. @@ -41,10 +48,10 @@ EVALUATORS_BY_MATCH_TYPE[QUALIFIED_MATCH_TYPE] = qualifiedEvaluator; * null if the given user attributes and condition can't be evaluated * TODO: Change to accept and object with named properties */ -export function evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { +function evaluate(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const conditionMatch = condition.match; if (typeof conditionMatch !== 'undefined' && MATCH_TYPES.indexOf(conditionMatch) === -1) { - logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); + logger?.warn(UNKNOWN_MATCH_TYPE, JSON.stringify(condition)); return null; } diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index eb4ec87eb..c87bb35d4 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -15,12 +15,11 @@ */ import sinon from 'sinon'; import { assert, expect } from 'chai'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, create } from 'lodash'; import { sprintf } from '../../utils/fns'; import * as bucketer from './'; import { LOG_LEVEL } from '../../utils/enums'; -import { createLogger } from '../../plugins/logger'; import projectConfig from '../../project_config/project_config'; import { getTestProjectConfig } from '../../tests/test_data'; import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; @@ -34,19 +33,33 @@ import { var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var testData = getTestProjectConfig(); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/bucketer', function () { describe('APIs', function () { describe('bucket', function () { var configObj; - var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + var createdLogger = createLogger(); var bucketerParams; beforeEach(function () { - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); }); afterEach(function () { - createdLogger.log.restore(); + createdLogger.info.restore(); + createdLogger.debug.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); }); describe('return values for bucketing (excluding groups)', function () { @@ -79,20 +92,13 @@ describe('lib/core/bucketer', function () { var decisionResponse = bucketer.bucket(bucketerParamsTest1); expect(decisionResponse.result).to.equal('111128'); - var bucketedUser_log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(bucketedUser_log1).to.equal( - sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'ppid1') - ); + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50, 'ppid1']); var bucketerParamsTest2 = cloneDeep(bucketerParams); bucketerParamsTest2.userId = 'ppid2'; expect(bucketer.bucket(bucketerParamsTest2).result).to.equal(null); - var notBucketedUser_log1 = buildLogMessageFromArgs(createdLogger.log.args[1]); - - expect(notBucketedUser_log1).to.equal( - sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'ppid2') - ); + expect(createdLogger.debug.args[1]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50000, 'ppid2']); }); }); @@ -139,28 +145,14 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal('551'); sinon.assert.calledTwice(bucketerStub); - sinon.assert.callCount(createdLogger.log, 3); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') - ); - - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal( - sprintf( - USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - 'BUCKETER', - 'testUser', - 'groupExperiment1', - '666' - ) - ); - - var log3 = buildLogMessageFromArgs(createdLogger.log.args[2]); - expect(log3).to.equal( - sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') - ); + sinon.assert.callCount(createdLogger.debug, 2); + sinon.assert.callCount(createdLogger.info, 1); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'testUser', 'groupExperiment1', '666']); + + expect(createdLogger.debug.args[1]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50, 'testUser']); }); it('should return decision response with variation null when a user is bucketed into a different grouped experiment than the one speicfied', function () { @@ -170,22 +162,12 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal(null); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledTwice(createdLogger.log); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '5000', 'testUser') - ); - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal( - sprintf( - USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - 'BUCKETER', - 'testUser', - 'groupExperiment1', - '666' - ) - ); + sinon.assert.calledOnce(createdLogger.debug); + sinon.assert.calledOnce(createdLogger.info); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 5000, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'testUser', 'groupExperiment1', '666']); }); it('should return decision response with variation null when a user is not bucketed into any experiments in the random group', function () { @@ -195,14 +177,12 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal(null); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledTwice(createdLogger.log); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'testUser') - ); - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal(sprintf(USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); + sinon.assert.calledOnce(createdLogger.debug); + sinon.assert.calledOnce(createdLogger.info); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50000, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666']); }); it('should return decision response with variation null when a user is bucketed into traffic space of deleted experiment within a random group', function () { @@ -212,14 +192,12 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal(null); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledTwice(createdLogger.log); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '9000', 'testUser') - ); - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal(sprintf(USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); + sinon.assert.calledOnce(createdLogger.debug); + sinon.assert.calledOnce(createdLogger.info); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 9000, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666']); }); it('should throw an error if group ID is not in the datafile', function () { @@ -254,10 +232,9 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal('553'); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledOnce(createdLogger.log); + sinon.assert.calledOnce(createdLogger.debug); - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal(sprintf(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '0', 'testUser')); + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 0, 'testUser']); }); it('should return decision response with variation null when a user does not fall into an experiment within an overlapping group', function () { @@ -301,8 +278,15 @@ describe('lib/core/bucketer', function () { it('should not log an invalid variation ID warning', function () { bucketer.bucket(bucketerParams) - const foundInvalidVariationWarning = createdLogger.log.getCalls().some((call) => { - const message = call.args[1]; + const calls = [ + ...createdLogger.debug.getCalls(), + ...createdLogger.info.getCalls(), + ...createdLogger.warn.getCalls(), + ...createdLogger.error.getCalls(), + ]; + + const foundInvalidVariationWarning = calls.some((call) => { + const message = call.args[0]; return message.includes('Bucketed into an invalid variation ID') }); expect(foundInvalidVariationWarning).to.equal(false); diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index 96d014dcf..88df2e818 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -19,7 +19,7 @@ */ import { sprintf } from '../../utils/fns'; import murmurhash from 'murmurhash'; -import { LogHandler } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { DecisionResponse, BucketerParams, @@ -30,11 +30,11 @@ import { import { LOG_LEVEL } from '../../utils/enums'; import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; -export const USER_NOT_IN_ANY_EXPERIMENT = '%s: User %s is not in any experiment of group %s.'; -export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = '%s: User %s is not in experiment %s of group %s.'; -export const USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP = '%s: User %s is in experiment %s of group %s.'; -export const USER_ASSIGNED_TO_EXPERIMENT_BUCKET = '%s: Assigned bucket %s to user with bucketing ID %s.'; -export const INVALID_VARIATION_ID = '%s: Bucketed into an invalid variation ID. Returning null.'; +export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.'; +export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.'; +export const USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is in experiment %s of group %s.'; +export const USER_ASSIGNED_TO_EXPERIMENT_BUCKET = 'Assigned bucket %s to user with bucketing ID %s.'; +export const INVALID_VARIATION_ID = 'Bucketed into an invalid variation ID. Returning null.'; const HASH_SEED = 1; const MAX_HASH_VALUE = Math.pow(2, 32); @@ -78,10 +78,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse // Return if user is not bucketed into any experiment if (bucketedExperimentId === null) { - bucketerParams.logger.log( - LOG_LEVEL.INFO, + bucketerParams.logger?.info( USER_NOT_IN_ANY_EXPERIMENT, - MODULE_NAME, bucketerParams.userId, groupId, ); @@ -99,10 +97,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse // Return if user is bucketed into a different experiment than the one specified if (bucketedExperimentId !== bucketerParams.experimentId) { - bucketerParams.logger.log( - LOG_LEVEL.INFO, + bucketerParams.logger?.info( USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -121,10 +117,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse } // Continue bucketing if user is bucketed into specified experiment - bucketerParams.logger.log( - LOG_LEVEL.INFO, + bucketerParams.logger?.info( USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -141,10 +135,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse const bucketingId = `${bucketerParams.bucketingId}${bucketerParams.experimentId}`; const bucketValue = _generateBucketValue(bucketingId); - bucketerParams.logger.log( - LOG_LEVEL.DEBUG, + bucketerParams.logger?.debug( USER_ASSIGNED_TO_EXPERIMENT_BUCKET, - MODULE_NAME, bucketValue, bucketerParams.userId, ); @@ -159,7 +151,7 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (entityId !== null) { if (!bucketerParams.variationIdMap[entityId]) { if (entityId) { - bucketerParams.logger.log(LOG_LEVEL.WARNING, INVALID_VARIATION_ID, MODULE_NAME); + bucketerParams.logger?.warn(INVALID_VARIATION_ID, MODULE_NAME); decideReasons.push([INVALID_VARIATION_ID, MODULE_NAME]); } return { @@ -180,21 +172,19 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse * @param {Group} group Group that experiment is in * @param {string} bucketingId Bucketing ID * @param {string} userId ID of user to be bucketed into experiment - * @param {LogHandler} logger Logger implementation + * @param {LoggerFacade} logger Logger implementation * @return {string|null} ID of experiment if user is bucketed into experiment within the group, null otherwise */ export const bucketUserIntoExperiment = function( group: Group, bucketingId: string, userId: string, - logger: LogHandler + logger?: LoggerFacade ): string | null { const bucketingKey = `${bucketingId}${group.id}`; const bucketValue = _generateBucketValue(bucketingKey); - logger.log( - LOG_LEVEL.DEBUG, + logger?.debug( USER_ASSIGNED_TO_EXPERIMENT_BUCKET, - MODULE_NAME, bucketValue, userId, ); diff --git a/lib/core/custom_attribute_condition_evaluator/index.tests.js b/lib/core/custom_attribute_condition_evaluator/index.tests.js index 5cf0e44c9..b17f3d3f7 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.tests.js +++ b/lib/core/custom_attribute_condition_evaluator/index.tests.js @@ -20,16 +20,17 @@ import { sprintf } from '../../utils/fns'; import { LOG_LEVEL, } from '../../utils/enums'; -import * as logging from '../../modules/logging'; import * as customAttributeEvaluator from './'; import { MISSING_ATTRIBUTE_VALUE, - OUT_OF_BOUNDS, - UNEXPECTED_CONDITION_VALUE, - UNEXPECTED_TYPE, UNEXPECTED_TYPE_NULL, } from '../../log_messages'; -import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; +import { + UNKNOWN_MATCH_TYPE, + UNEXPECTED_TYPE, + OUT_OF_BOUNDS, + UNEXPECTED_CONDITION_VALUE, +} from '../../error_messages'; var browserConditionSafari = { name: 'browser_type', @@ -56,19 +57,29 @@ var getMockUserContext = (attributes) => ({ getAttributes: () => ({ ... (attributes || {})}) }); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}); + describe('lib/core/custom_attribute_condition_evaluator', function() { - var stubLogHandler; + var mockLogger = createLogger(); beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); + sinon.stub(mockLogger, 'error'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'warn'); }); afterEach(function() { - logging.resetLogger(); + mockLogger.error.restore(); + mockLogger.debug.restore(); + mockLogger.info.restore(); + mockLogger.warn.restore(); }); it('should return true when the attributes pass the audience conditions and no match type is provided', function() { @@ -76,7 +87,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { browser_type: 'safari', }; - assert.isTrue(customAttributeEvaluator.evaluate(browserConditionSafari, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes))); }); it('should return false when the attributes do not pass the audience conditions and no match type is provided', function() { @@ -84,7 +95,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { browser_type: 'firefox', }; - assert.isFalse(customAttributeEvaluator.evaluate(browserConditionSafari, getMockUserContext(userAttributes))); + assert.isFalse(customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes))); }); it('should evaluate different typed attributes', function() { @@ -95,23 +106,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { pi_value: 3.14, }; - assert.isTrue(customAttributeEvaluator.evaluate(browserConditionSafari, getMockUserContext(userAttributes))); - assert.isTrue(customAttributeEvaluator.evaluate(booleanCondition, getMockUserContext(userAttributes))); - assert.isTrue(customAttributeEvaluator.evaluate(integerCondition, getMockUserContext(userAttributes))); - assert.isTrue(customAttributeEvaluator.evaluate(doubleCondition, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(booleanCondition, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(integerCondition, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(doubleCondition, getMockUserContext(userAttributes))); }); it('should log and return null when condition has an invalid match property', function() { var invalidMatchCondition = { match: 'weird', name: 'weird_condition', type: 'custom_attribute', value: 'hi' }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( invalidMatchCondition, getMockUserContext({ weird_condition: 'bye' }) ); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(UNKNOWN_MATCH_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidMatchCondition))); + sinon.assert.calledOnce(mockLogger.warn); + assert.strictEqual(mockLogger.warn.args[0][0], UNKNOWN_MATCH_TYPE); + assert.strictEqual(mockLogger.warn.args[0][1], JSON.stringify(invalidMatchCondition)); }); describe('exists match type', function() { @@ -122,33 +132,36 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return false if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(existsCondition, getMockUserContext({})); assert.isFalse(result); - sinon.assert.notCalled(stubLogHandler.log); + sinon.assert.notCalled(mockLogger.debug); + sinon.assert.notCalled(mockLogger.info); + sinon.assert.notCalled(mockLogger.warn); + sinon.assert.notCalled(mockLogger.error); }); it('should return false if the user-provided value is undefined', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: undefined })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: undefined })); assert.isFalse(result); }); it('should return false if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: null })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: null })); assert.isFalse(result); }); it('should return true if the user-provided value is a string', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: 'hi' })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: 'hi' })); assert.isTrue(result); }); it('should return true if the user-provided value is a number', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: 10 })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: 10 })); assert.isTrue(result); }); it('should return true if the user-provided value is a boolean', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: true })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: true })); assert.isTrue(result); }); }); @@ -163,7 +176,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator().evaluate( exactStringCondition, getMockUserContext({ favorite_constellation: 'Lacerta' }) ); @@ -171,7 +184,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator().evaluate( exactStringCondition, getMockUserContext({ favorite_constellation: 'The Big Dipper' }) ); @@ -185,20 +198,19 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: [], }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( invalidExactCondition, getMockUserContext({ favorite_constellation: 'Lacerta' }) ); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidExactCondition))); + sinon.assert.calledOnce(mockLogger.warn); + assert.strictEqual(mockLogger.warn.args[0][0], UNEXPECTED_CONDITION_VALUE); + assert.strictEqual(mockLogger.warn.args[0][1], JSON.stringify(invalidExactCondition)); }); it('should log and return null if the user-provided value is of a different type than the condition value', function() { var unexpectedTypeUserAttributes = { favorite_constellation: false }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes) ); @@ -206,58 +218,42 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; var userValueType = typeof userValue; - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactStringCondition, getMockUserContext({ favorite_constellation: null }) ); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(exactStringCondition), exactStringCondition.name]); }); it('should log and return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(exactStringCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactStringCondition, getMockUserContext({})); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(MISSING_ATTRIBUTE_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [MISSING_ATTRIBUTE_VALUE, JSON.stringify(exactStringCondition), exactStringCondition.name]); }); it('should log and return null if the user-provided value is of an unexpected type', function() { var unexpectedTypeUserAttributes = { favorite_constellation: [] }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes) ); assert.isNull(result); var userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; var userValueType = typeof userValue; - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name]); }); }); @@ -270,25 +266,25 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 9000 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 9000 })); assert.isTrue(result); }); it('should return false if the user-provided value is not equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 8000 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 8000 })); assert.isFalse(result); }); it('should log and return null if the user-provided value is of a different type than the condition value', function() { var unexpectedTypeUserAttributes1 = { lasers_count: 'yes' }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes1) ); assert.isNull(result); var unexpectedTypeUserAttributes2 = { lasers_count: '1000' }; - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes2) ); @@ -298,50 +294,31 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValueType1 = typeof userValue1; var userValue2 = unexpectedTypeUserAttributes2[exactNumberCondition.name]; var userValueType2 = typeof userValue2; - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); - - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType1, exactNumberCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType2, exactNumberCondition.name) - ); + assert.strictEqual(2, mockLogger.warn.callCount); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(exactNumberCondition), userValueType1, exactNumberCondition.name]); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(exactNumberCondition), userValueType2, exactNumberCondition.name]); }); it('should log and return null if the user-provided number value is out of bounds', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Infinity })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Infinity })); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactNumberCondition, getMockUserContext({ lasers_count: -Math.pow(2, 53) - 2 }) ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) - ); + assert.deepEqual(mockLogger.warn.args[0], [OUT_OF_BOUNDS, JSON.stringify(exactNumberCondition), exactNumberCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [OUT_OF_BOUNDS, JSON.stringify(exactNumberCondition), exactNumberCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({})); assert.isNull(result); }); @@ -352,7 +329,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Infinity, }; - var result = customAttributeEvaluator.evaluate(invalidValueCondition1, getMockUserContext({ lasers_count: 9000 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition1, getMockUserContext({ lasers_count: 9000 })); assert.isNull(result); var invalidValueCondition2 = { @@ -361,23 +338,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Math.pow(2, 53) + 2, }; - result = customAttributeEvaluator.evaluate(invalidValueCondition2, getMockUserContext({ lasers_count: 9000 })); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition2, getMockUserContext({ lasers_count: 9000 })); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition1)) - ); - assert.strictEqual( - logMessage2, - sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition2)) - ); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition1)]); + + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition2)]); }); }); @@ -390,22 +358,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({ did_register_user: false })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({ did_register_user: false })); assert.isTrue(result); }); it('should return false if the user-provided value is not equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({ did_register_user: true })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({ did_register_user: true })); assert.isFalse(result); }); it('should return null if the user-provided value is of a different type than the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({ did_register_user: 10 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({ did_register_user: 10 })); assert.isNull(result); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -420,7 +388,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the condition value is a substring of the user-provided value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( substringCondition, getMockUserContext({ headline_text: 'Limited time, buy now!', @@ -430,7 +398,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not a substring of the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( substringCondition, getMockUserContext({ headline_text: 'Breaking news!', @@ -441,20 +409,16 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided value is not a string', function() { var unexpectedTypeUserAttributes = { headline_text: 10 }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( substringCondition, getMockUserContext(unexpectedTypeUserAttributes) ); assert.isNull(result); var userValue = unexpectedTypeUserAttributes[substringCondition.name]; var userValueType = typeof userValue; - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), userValueType, substringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(substringCondition), userValueType, substringCondition.name]); }); it('should log and return null if the condition value is not a string', function() { @@ -465,31 +429,23 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { value: 10, }; - var result = customAttributeEvaluator.evaluate(nonStringCondition, getMockUserContext({ headline_text: 'hello' })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(nonStringCondition, getMockUserContext({ headline_text: 'hello' })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(nonStringCondition)) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(nonStringCondition)]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(substringCondition, getMockUserContext({ headline_text: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(substringCondition, getMockUserContext({ headline_text: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), substringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(substringCondition), substringCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(substringCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(substringCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -503,7 +459,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is greater than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: 58.4, @@ -513,7 +469,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not greater than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: 20, @@ -524,14 +480,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided value is not a number', function() { var unexpectedTypeUserAttributes1 = { meters_travelled: 'a long way' }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext(unexpectedTypeUserAttributes1) ); assert.isNull(result); var unexpectedTypeUserAttributes2 = { meters_travelled: '1000' }; - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext(unexpectedTypeUserAttributes2) ); @@ -541,65 +497,43 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValueType1 = typeof userValue1; var userValue2 = unexpectedTypeUserAttributes2[gtCondition.name]; var userValueType2 = typeof userValue2; - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); - - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType1, gtCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType2, gtCondition.name) - ); + assert.strictEqual(2, mockLogger.warn.callCount); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(gtCondition), userValueType1, gtCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(gtCondition), userValueType2, gtCondition.name]); }); it('should log and return null if the user-provided number value is out of bounds', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: -Infinity }) ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2 }) ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) - ); + assert.deepEqual(mockLogger.warn.args[0], [OUT_OF_BOUNDS, JSON.stringify(gtCondition), gtCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [OUT_OF_BOUNDS, JSON.stringify(gtCondition), gtCondition.name]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(gtCondition, getMockUserContext({ meters_travelled: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(gtCondition, getMockUserContext({ meters_travelled: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(gtCondition), gtCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(gtCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(gtCondition, getMockUserContext({})); assert.isNull(result); }); @@ -611,23 +545,20 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Infinity, }; - var result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = null; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = Math.pow(2, 53) + 2; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); - sinon.assert.calledThrice(stubLogHandler.log); - var logMessage = stubLogHandler.log.args[2][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) - ); + sinon.assert.calledThrice(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[2], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)]); }); }); @@ -640,7 +571,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is less than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: 10, @@ -650,7 +581,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not less than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: 64.64, @@ -661,14 +592,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided value is not a number', function() { var unexpectedTypeUserAttributes1 = { meters_travelled: true }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext(unexpectedTypeUserAttributes1) ); assert.isNull(result); var unexpectedTypeUserAttributes2 = { meters_travelled: '48.2' }; - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext(unexpectedTypeUserAttributes2) ); @@ -678,24 +609,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValueType1 = typeof userValue1; var userValue2 = unexpectedTypeUserAttributes2[ltCondition.name]; var userValueType2 = typeof userValue2; - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); - - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType1, ltCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType2, ltCondition.name) - ); + + assert.strictEqual(2, mockLogger.warn.callCount); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(ltCondition), userValueType1, ltCondition.name]); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(ltCondition), userValueType2, ltCondition.name]); }); it('should log and return null if the user-provided number value is out of bounds', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: Infinity, @@ -703,7 +624,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2, @@ -711,36 +632,23 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) - ); + assert.deepEqual(mockLogger.warn.args[0], [OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(ltCondition, getMockUserContext({ meters_travelled: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(ltCondition, getMockUserContext({ meters_travelled: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(ltCondition), ltCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(ltCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(ltCondition, getMockUserContext({})); assert.isNull(result); }); @@ -752,23 +660,19 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Infinity, }; - var result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = {}; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = Math.pow(2, 53) + 2; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); - sinon.assert.calledThrice(stubLogHandler.log); - var logMessage = stubLogHandler.log.args[2][1]; - assert.strictEqual( - logMessage, - sprintf(UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) - ); + sinon.assert.calledThrice(mockLogger.warn); + assert.deepEqual(mockLogger.warn.args[2], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)]); }); }); describe('less than or equal to match type', function() { @@ -780,7 +684,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return false if the user-provided value is greater than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( leCondition, getMockUserContext({ meters_travelled: 48.3, @@ -792,7 +696,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should return true if the user-provided value is less than or equal to the condition value', function() { var versions = [48, 48.2]; for (let userValue of versions) { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( leCondition, getMockUserContext({ meters_travelled: userValue, @@ -813,7 +717,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return false if the user-provided value is less than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( geCondition, getMockUserContext({ meters_travelled: 48, @@ -825,7 +729,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should return true if the user-provided value is less than or equal to the condition value', function() { var versions = [100, 48.2]; for (let userValue of versions) { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( geCondition, getMockUserContext({ meters_travelled: userValue, @@ -855,7 +759,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvergtCondition, getMockUserContext({ app_version: userVersion, @@ -879,7 +783,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvergtCondition, getMockUserContext({ app_version: userVersion, @@ -890,7 +794,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should log and return null if the user-provided version is not a string', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvergtCondition, getMockUserContext({ app_version: 22, @@ -898,7 +802,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvergtCondition, getMockUserContext({ app_version: false, @@ -906,30 +810,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual( - stubLogHandler.log.args[0][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_gt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "number" was passed for user attribute "app_version".' - ); - assert.strictEqual( - stubLogHandler.log.args[1][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_gt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "boolean" was passed for user attribute "app_version".' - ); + assert.strictEqual(2, mockLogger.warn.callCount); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(semvergtCondition), 'number', 'app_version']); + + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(semvergtCondition), 'boolean', 'app_version']); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(semvergtCondition, getMockUserContext({ app_version: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvergtCondition, getMockUserContext({ app_version: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - sinon.assert.calledWithExactly( - stubLogHandler.log, - LOG_LEVEL.DEBUG, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_gt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a null value was passed for user attribute "app_version".' - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(semvergtCondition), 'app_version']); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(semvergtCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvergtCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -956,7 +852,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemverltCondition, getMockUserContext({ app_version: userVersion, @@ -978,7 +874,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemverltCondition, getMockUserContext({ app_version: userVersion, @@ -989,7 +885,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should log and return null if the user-provided version is not a string', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semverltCondition, getMockUserContext({ app_version: 22, @@ -997,7 +893,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semverltCondition, getMockUserContext({ app_version: false, @@ -1005,30 +901,21 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual( - stubLogHandler.log.args[0][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_lt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "number" was passed for user attribute "app_version".' - ); - assert.strictEqual( - stubLogHandler.log.args[1][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_lt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "boolean" was passed for user attribute "app_version".' - ); + assert.strictEqual(2, mockLogger.warn.callCount); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(semverltCondition), 'number', 'app_version']); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(semverltCondition), 'boolean', 'app_version']); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(semverltCondition, getMockUserContext({ app_version: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semverltCondition, getMockUserContext({ app_version: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - sinon.assert.calledWithExactly( - stubLogHandler.log, - LOG_LEVEL.DEBUG, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_lt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a null value was passed for user attribute "app_version".' - ); + sinon.assert.calledOnce(mockLogger.debug); + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(semverltCondition), 'app_version']); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(semverltCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semverltCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -1054,7 +941,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1076,7 +963,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1087,7 +974,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should log and return null if the user-provided version is not a string', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvereqCondition, getMockUserContext({ app_version: 22, @@ -1095,7 +982,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvereqCondition, getMockUserContext({ app_version: false, @@ -1103,30 +990,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual( - stubLogHandler.log.args[0][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_eq","name":"app_version","type":"custom_attribute","value":"2.0"} evaluated to UNKNOWN because a value of type "number" was passed for user attribute "app_version".' - ); - assert.strictEqual( - stubLogHandler.log.args[1][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_eq","name":"app_version","type":"custom_attribute","value":"2.0"} evaluated to UNKNOWN because a value of type "boolean" was passed for user attribute "app_version".' - ); + assert.strictEqual(2, mockLogger.warn.callCount); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(semvereqCondition), 'number', 'app_version']); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(semvereqCondition), 'boolean', 'app_version']); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(semvereqCondition, getMockUserContext({ app_version: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvereqCondition, getMockUserContext({ app_version: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - sinon.assert.calledWithExactly( - stubLogHandler.log, - LOG_LEVEL.DEBUG, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_eq","name":"app_version","type":"custom_attribute","value":"2.0"} evaluated to UNKNOWN because a null value was passed for user attribute "app_version".' - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.strictEqual(mockLogger.debug.args[0][0], UNEXPECTED_TYPE_NULL); + assert.strictEqual(mockLogger.debug.args[0][1], JSON.stringify(semvereqCondition)); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(semvereqCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvereqCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -1150,7 +1029,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1173,7 +1052,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1184,7 +1063,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return true if the user-provided version is equal to the condition version', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semverleCondition, getMockUserContext({ app_version: '2.0', @@ -1215,7 +1094,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1237,7 +1116,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, diff --git a/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts index ab30a214d..0a3c0b0a6 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/lib/core/custom_attribute_condition_evaluator/index.ts @@ -13,24 +13,24 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { getLogger } from '../../modules/logging'; import { Condition, OptimizelyUserContext } from '../../shared_types'; import fns from '../../utils/fns'; import { compareVersion } from '../../utils/semantic_version'; import { MISSING_ATTRIBUTE_VALUE, - OUT_OF_BOUNDS, - UNEXPECTED_CONDITION_VALUE, - UNEXPECTED_TYPE, UNEXPECTED_TYPE_NULL, } from '../../log_messages'; -import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; +import { + OUT_OF_BOUNDS, + UNEXPECTED_TYPE, + UNEXPECTED_CONDITION_VALUE, + UNKNOWN_MATCH_TYPE +} from '../../error_messages'; +import { LoggerFacade } from '../../logging/logger'; const MODULE_NAME = 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR'; -const logger = getLogger(); - const EXACT_MATCH_TYPE = 'exact'; const EXISTS_MATCH_TYPE = 'exists'; const GREATER_OR_EQUAL_THAN_MATCH_TYPE = 'ge'; @@ -59,7 +59,8 @@ const MATCH_TYPES = [ SEMVER_GREATER_OR_EQUAL_THAN_MATCH_TYPE ]; -type ConditionEvaluator = (condition: Condition, user: OptimizelyUserContext) => boolean | null; +type ConditionEvaluator = (condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade) => boolean | null; +type Evaluator = { evaluate: (condition: Condition, user: OptimizelyUserContext) => boolean | null; } const EVALUATORS_BY_MATCH_TYPE: { [conditionType: string]: ConditionEvaluator | undefined } = {}; EVALUATORS_BY_MATCH_TYPE[EXACT_MATCH_TYPE] = exactEvaluator; @@ -75,6 +76,14 @@ EVALUATORS_BY_MATCH_TYPE[SEMVER_GREATER_OR_EQUAL_THAN_MATCH_TYPE] = semverGreate EVALUATORS_BY_MATCH_TYPE[SEMVER_LESS_THAN_MATCH_TYPE] = semverLessThanEvaluator; EVALUATORS_BY_MATCH_TYPE[SEMVER_LESS_OR_EQUAL_THAN_MATCH_TYPE] = semverLessThanOrEqualEvaluator; +export const getEvaluator = (logger?: LoggerFacade): Evaluator => { + return { + evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { + return evaluate(condition, user, logger); + } + }; +} + /** * Given a custom attribute audience condition and user attributes, evaluate the * condition against the attributes. @@ -84,18 +93,18 @@ EVALUATORS_BY_MATCH_TYPE[SEMVER_LESS_OR_EQUAL_THAN_MATCH_TYPE] = semverLessThanO * null if the given user attributes and condition can't be evaluated * TODO: Change to accept and object with named properties */ -export function evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { +function evaluate(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const conditionMatch = condition.match; if (typeof conditionMatch !== 'undefined' && MATCH_TYPES.indexOf(conditionMatch) === -1) { - logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); + logger?.warn(UNKNOWN_MATCH_TYPE, JSON.stringify(condition)); return null; } const attributeKey = condition.name; if (!userAttributes.hasOwnProperty(attributeKey) && conditionMatch != EXISTS_MATCH_TYPE) { - logger.debug( - MISSING_ATTRIBUTE_VALUE, MODULE_NAME, JSON.stringify(condition), attributeKey + logger?.debug( + MISSING_ATTRIBUTE_VALUE, JSON.stringify(condition), attributeKey ); return null; } @@ -107,7 +116,7 @@ export function evaluate(condition: Condition, user: OptimizelyUserContext): boo evaluatorForMatch = EVALUATORS_BY_MATCH_TYPE[conditionMatch] || exactEvaluator; } - return evaluatorForMatch(condition, user); + return evaluatorForMatch(condition, user, logger); } /** @@ -130,7 +139,7 @@ function isValueTypeValidForExactConditions(value: unknown): boolean { * if there is a mismatch between the user attribute type and the condition value * type */ -function exactEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function exactEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const conditionValue = condition.value; const conditionValueType = typeof conditionValue; @@ -142,29 +151,29 @@ function exactEvaluator(condition: Condition, user: OptimizelyUserContext): bool !isValueTypeValidForExactConditions(conditionValue) || (fns.isNumber(conditionValue) && !fns.isSafeInteger(conditionValue)) ) { - logger.warn( - UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return null; } if (userValue === null) { - logger.debug( - UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return null; } if (!isValueTypeValidForExactConditions(userValue) || conditionValueType !== userValueType) { - logger.warn( - UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return null; } if (fns.isNumber(userValue) && !fns.isSafeInteger(userValue)) { - logger.warn( - OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.warn( + OUT_OF_BOUNDS, JSON.stringify(condition), conditionName ); return null; } @@ -181,7 +190,7 @@ function exactEvaluator(condition: Condition, user: OptimizelyUserContext): bool * 2) the user attribute value is neither null nor undefined * Returns false otherwise */ -function existsEvaluator(condition: Condition, user: OptimizelyUserContext): boolean { +function existsEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; return typeof userValue !== 'undefined' && userValue !== null; @@ -194,7 +203,7 @@ function existsEvaluator(condition: Condition, user: OptimizelyUserContext): boo * @returns {?boolean} true if values are valid, * false if values are not valid */ -function validateValuesForNumericCondition(condition: Condition, user: OptimizelyUserContext): boolean { +function validateValuesForNumericCondition(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean { const userAttributes = user.getAttributes(); const conditionName = condition.name; const userValue = userAttributes[conditionName]; @@ -202,29 +211,29 @@ function validateValuesForNumericCondition(condition: Condition, user: Optimizel const conditionValue = condition.value; if (conditionValue === null || !fns.isSafeInteger(conditionValue)) { - logger.warn( - UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return false; } if (userValue === null) { - logger.debug( - UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return false; } if (!fns.isNumber(userValue)) { - logger.warn( - UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return false; } if (!fns.isSafeInteger(userValue)) { - logger.warn( - OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.warn( + OUT_OF_BOUNDS, JSON.stringify(condition), conditionName ); return false; } @@ -240,12 +249,12 @@ function validateValuesForNumericCondition(condition: Condition, user: Optimizel * null if the condition value isn't a number or the user attribute value * isn't a number */ -function greaterThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function greaterThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } return userValue! > conditionValue; @@ -260,12 +269,12 @@ function greaterThanEvaluator(condition: Condition, user: OptimizelyUserContext) * null if the condition value isn't a number or the user attribute value isn't a * number */ -function greaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function greaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } @@ -281,12 +290,12 @@ function greaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserC * null if the condition value isn't a number or the user attribute value isn't a * number */ -function lessThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function lessThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } @@ -302,12 +311,12 @@ function lessThanEvaluator(condition: Condition, user: OptimizelyUserContext): b * null if the condition value isn't a number or the user attribute value isn't a * number */ -function lessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function lessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } @@ -323,7 +332,7 @@ function lessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserCont * null if the condition value isn't a string or the user attribute value * isn't a string */ -function substringEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function substringEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const conditionName = condition.name; const userValue = userAttributes[condition.name]; @@ -331,22 +340,22 @@ function substringEvaluator(condition: Condition, user: OptimizelyUserContext): const conditionValue = condition.value; if (typeof conditionValue !== 'string') { - logger.warn( - UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return null; } if (userValue === null) { - logger.debug( - UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return null; } if (typeof userValue !== 'string') { - logger.warn( - UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return null; } @@ -361,7 +370,7 @@ function substringEvaluator(condition: Condition, user: OptimizelyUserContext): * @returns {?number} returns compareVersion result * null if the user attribute version has an invalid type */ -function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserContext): number | null { +function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): number | null { const userAttributes = user.getAttributes(); const conditionName = condition.name; const userValue = userAttributes[conditionName]; @@ -369,27 +378,27 @@ function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserConte const conditionValue = condition.value; if (typeof conditionValue !== 'string') { - logger.warn( - UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return null; } if (userValue === null) { - logger.debug( - UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return null; } if (typeof userValue !== 'string') { - logger.warn( - UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return null; } - return compareVersion(conditionValue, userValue); + return compareVersion(conditionValue, userValue, logger); } /** @@ -400,8 +409,8 @@ function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserConte * false if the user attribute version is not equal (!==) to the condition version, * null if the user attribute version has an invalid type */ -function semverEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -416,8 +425,8 @@ function semverEqualEvaluator(condition: Condition, user: OptimizelyUserContext) * false if the user attribute version is not greater than the condition version, * null if the user attribute version has an invalid type */ -function semverGreaterThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverGreaterThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -432,8 +441,8 @@ function semverGreaterThanEvaluator(condition: Condition, user: OptimizelyUserCo * false if the user attribute version is not less than the condition version, * null if the user attribute version has an invalid type */ -function semverLessThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverLessThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -448,8 +457,8 @@ function semverLessThanEvaluator(condition: Condition, user: OptimizelyUserConte * false if the user attribute version is not greater than or equal to the condition version, * null if the user attribute version has an invalid type */ -function semverGreaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverGreaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -464,11 +473,10 @@ function semverGreaterThanOrEqualEvaluator(condition: Condition, user: Optimizel * false if the user attribute version is not less than or equal to the condition version, * null if the user attribute version has an invalid type */ -function semverLessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverLessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } return result <= 0; - } diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 046850db9..39d8889fd 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -24,7 +24,6 @@ import { LOG_LEVEL, DECISION_SOURCES, } from '../../utils/enums'; -import { createLogger } from '../../plugins/logger'; import { getForwardingEventProcessor } from '../../event_processor/forwarding_event_processor'; import { createNotificationCenter } from '../../notification_center'; import Optimizely from '../../optimizely'; @@ -40,11 +39,41 @@ import { getTestProjectConfig, getTestProjectConfigWithFeatures, } from '../../tests/test_data'; +import { + AUDIENCE_EVALUATION_RESULT_COMBINED, + EVALUATING_AUDIENCES_COMBINED, + USER_FORCED_IN_VARIATION, + USER_HAS_NO_FORCED_VARIATION, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_IN_EXPERIMENT, + EXPERIMENT_NOT_RUNNING, + RETURNING_STORED_VARIATION, + FEATURE_HAS_NO_EXPERIMENTS, + NO_ROLLOUT_EXISTS, + USER_BUCKETED_INTO_TARGETING_RULE, + USER_IN_ROLLOUT, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_NOT_IN_ROLLOUT, + VALID_BUCKETING_ID, + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND +} from '../../log_messages'; +import { mock } from 'node:test'; +import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from '../../error_messages'; var testData = getTestProjectConfig(); var testDataWithFeatures = getTestProjectConfigWithFeatures(); var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/decision_service', function() { describe('APIs', function() { var configObj = projectConfig.createProjectConfig(cloneDeep(testData)); @@ -56,7 +85,11 @@ describe('lib/core/decision_service', function() { beforeEach(function() { bucketerStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(mockLogger, 'log'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); + decisionServiceInstance = createDecisionService({ logger: mockLogger, }); @@ -64,7 +97,10 @@ describe('lib/core/decision_service', function() { afterEach(function() { bucketer.bucket.restore(); - mockLogger.log.restore(); + mockLogger.debug.restore(); + mockLogger.info.restore(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); describe('#getVariation', function() { @@ -99,15 +135,12 @@ describe('lib/core/decision_service', function() { decisionServiceInstance.getVariation(configObj, experiment, user).result ); sinon.assert.notCalled(bucketerStub); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User user2 is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: User user2 is forced in variation variationWithAudience.' - ); + assert.strictEqual(1, mockLogger.debug.callCount); + assert.strictEqual(1, mockLogger.info.callCount); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'user2']); + + assert.deepEqual(mockLogger.info.args[0], [USER_FORCED_IN_VARIATION, 'user2', 'variationWithAudience']); }); it('should return null if the user does not meet audience conditions', function() { @@ -120,23 +153,14 @@ describe('lib/core/decision_service', function() { assert.isNull( decisionServiceInstance.getVariation(configObj, experiment, user, { foo: 'bar' }).result ); - assert.strictEqual(4, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User user3 is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[2]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'DECISION_SERVICE: User user3 does not meet conditions to be in experiment testExperimentWithAudiences.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'user3']); + + assert.deepEqual(mockLogger.debug.args[1], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE']); + + assert.deepEqual(mockLogger.info.args[1], [USER_NOT_IN_EXPERIMENT, 'user3', 'testExperimentWithAudiences']); }); it('should return null if the experiment is not running', function() { @@ -148,11 +172,9 @@ describe('lib/core/decision_service', function() { experiment = configObj.experimentIdMap['133337']; assert.isNull(decisionServiceInstance.getVariation(configObj, experiment, user).result); sinon.assert.notCalled(bucketerStub); - assert.strictEqual(1, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Experiment testExperimentNotRunning is not running.' - ); + assert.strictEqual(1, mockLogger.info.callCount); + + assert.deepEqual(mockLogger.info.args[0], [EXPERIMENT_NOT_RUNNING, 'testExperimentNotRunning']); }); describe('when attributes.$opt_experiment_bucket_map is supplied', function() { @@ -240,14 +262,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "control" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'control', 'testExperiment', 'decision_service_user']); }); it('should bucket if there was no prevously bucketed variation', function() { @@ -328,14 +346,20 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: User decision_service_user was previously bucketed into variation with ID not valid variation for experiment testExperiment, but no matching variation was found.' + // assert.strictEqual( + // buildLogMessageFromArgs(mockLogger.log.args[0]), + // 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' + // ); + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + sinon.assert.calledWith( + mockLogger.info, + SAVED_VARIATION_NOT_FOUND, + 'decision_service_user', + 'not valid variation', + 'testExperiment' ); + // make sure we save the decision sinon.assert.calledWith(userProfileSaveStub, { user_id: 'decision_service_user', @@ -366,7 +390,7 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); - assert.strictEqual(5, mockLogger.log.callCount); + sinon.assert.calledWith(userProfileServiceInstance.save, { user_id: 'decision_service_user', experiment_bucket_map: { @@ -375,14 +399,11 @@ describe('lib/core/decision_service', function() { }, }, }); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[4]), - 'DECISION_SERVICE: Saved user profile for user "decision_service_user".' - ); + + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.lastCall.args, [SAVED_USER_VARIATION, 'decision_service_user']); }); it('should log an error message if "lookup" throws an error', function() { @@ -401,14 +422,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); // should still go through with bucketing - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Error while looking up user profile for user ID "decision_service_user": I am an error.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); + + assert.deepEqual(mockLogger.error.args[0], [USER_PROFILE_LOOKUP_ERROR, 'decision_service_user', 'I am an error']); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); }); it('should log an error message if "save" throws an error', function() { @@ -428,15 +445,10 @@ describe('lib/core/decision_service', function() { sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); // should still go through with bucketing - assert.strictEqual(5, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[4]), - 'DECISION_SERVICE: Error while saving user profile for user ID "decision_service_user": I am an error.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.error.args[0], [USER_PROFILE_SAVE_ERROR, 'decision_service_user', 'I am an error']); // make sure that we save the decision sinon.assert.calledWith(userProfileSaveStub, { @@ -483,14 +495,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "variation" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user']); }); it('should ignore attributes for a different experiment id', function() { @@ -528,14 +536,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "control" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'control', 'testExperiment', 'decision_service_user']); }); it('should use attributes when the userProfileLookup variations for other experiments', function() { @@ -573,14 +577,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "variation" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user']); }); it('should use attributes when the userProfileLookup returns null', function() { @@ -609,14 +609,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "variation" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user']); }); }); }); @@ -655,8 +651,6 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(bucketerParams, expectedParams); - - sinon.assert.notCalled(mockLogger.log); }); }); @@ -692,15 +686,10 @@ describe('lib/core/decision_service', function() { '' ).result ); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to TRUE.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'TRUE']); }); it('should return decision response with result true when experiment has no audience', function() { @@ -716,15 +705,9 @@ describe('lib/core/decision_service', function() { ); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(true)); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperiment": [].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperiment collectively evaluated to TRUE.' - ); + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperiment', JSON.stringify([])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperiment', 'TRUE']); }); it('should return decision response with result false when audience conditions can not be evaluated', function() { @@ -740,15 +723,9 @@ describe('lib/core/decision_service', function() { ); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(false)); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.' - ); + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE']); }); it('should return decision response with result false when audience conditions are not met', function() { @@ -764,15 +741,10 @@ describe('lib/core/decision_service', function() { ); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(false)); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE']); }); }); @@ -1202,7 +1174,11 @@ describe('lib/core/decision_service', function() { }; beforeEach(function() { - sinon.stub(mockLogger, 'log'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); decisionService = createDecisionService({ logger: mockLogger, @@ -1210,7 +1186,10 @@ describe('lib/core/decision_service', function() { }); afterEach(function() { - mockLogger.log.restore(); + mockLogger.debug.restore(); + mockLogger.info.restore(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); it('should return userId if bucketingId is not defined in user attributes', function() { @@ -1220,17 +1199,13 @@ describe('lib/core/decision_service', function() { it('should log warning in case of invalid bucketingId', function() { assert.strictEqual(userId, decisionService.getBucketingId(userId, userAttributesWithInvalidBucketingId)); - assert.strictEqual(1, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: BucketingID attribute is not a string. Defaulted to userId' - ); + assert.deepEqual(mockLogger.warn.args[0], [BUCKETING_ID_NOT_STRING]); }); it('should return correct bucketingId when provided in attributes', function() { assert.strictEqual('123456789', decisionService.getBucketingId(userId, userAttributesWithBucketingId)); - assert.strictEqual(1, mockLogger.log.callCount); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[0]), 'DECISION_SERVICE: BucketingId is valid: "123456789"'); + assert.strictEqual(1, mockLogger.debug.callCount); + assert.deepEqual(mockLogger.debug.args[0], [VALID_BUCKETING_ID, '123456789']); }); }); @@ -1249,7 +1224,11 @@ describe('lib/core/decision_service', function() { beforeEach(function() { configObj = projectConfig.createProjectConfig(cloneDeep(testDataWithFeatures)); sandbox = sinon.sandbox.create(); - sandbox.stub(mockLogger, 'log'); + sandbox.stub(mockLogger, 'debug'); + sandbox.stub(mockLogger, 'info'); + sandbox.stub(mockLogger, 'warn'); + sandbox.stub(mockLogger, 'error'); + decisionServiceInstance = createDecisionService({ logger: mockLogger, }); @@ -1527,10 +1506,8 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.lastCall.args), - 'DECISION_SERVICE: User user1 is not in rollout of feature test_feature_for_experiment.' - ); + + assert.deepEqual(mockLogger.debug.lastCall.args, [USER_NOT_IN_ROLLOUT, 'user1', 'test_feature_for_experiment']); }); }); }); @@ -1623,10 +1600,8 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.lastCall.args), - 'DECISION_SERVICE: User user1 is not in rollout of feature feature_with_group.' - ); + + assert.deepEqual(mockLogger.debug.lastCall.args, [USER_NOT_IN_ROLLOUT, 'user1', 'feature_with_group']); }); it('returns null decision for group experiment not referenced by the feature', function() { @@ -1639,10 +1614,9 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: There is no rollout of feature %s.', - 'DECISION_SERVICE', 'feature_exp_no_traffic' + mockLogger.debug, + NO_ROLLOUT_EXISTS, + 'feature_exp_no_traffic' ); }); }); @@ -1773,23 +1747,20 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s meets conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'test_feature' + mockLogger.debug, + USER_IN_ROLLOUT, + 'user1', 'test_feature' ); }); }); @@ -1911,22 +1882,19 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s does not meet conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 'Everyone Else' + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 'Everyone Else' ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'test_feature' + mockLogger.debug, + USER_IN_ROLLOUT, + 'user1', 'test_feature' ); }); }); @@ -1954,16 +1922,14 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s does not meet conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is not in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'test_feature' + mockLogger.debug, + USER_NOT_IN_ROLLOUT, + 'user1', 'test_feature' ); }); }); @@ -2096,22 +2062,19 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s meets conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 'Everyone Else' + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 'Everyone Else' ); }); }); @@ -2215,16 +2178,14 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 'Everyone Else' + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 'Everyone Else' ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'shared_feature' + mockLogger.debug, + USER_IN_ROLLOUT, + 'user1', 'shared_feature' ); }); }); @@ -2249,16 +2210,14 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: Feature %s is not attached to any experiments.', - 'DECISION_SERVICE', 'unused_flag' + mockLogger.debug, + FEATURE_HAS_NO_EXPERIMENTS, + 'unused_flag' ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: There is no rollout of feature %s.', - 'DECISION_SERVICE', 'unused_flag' + mockLogger.debug, + NO_ROLLOUT_EXISTS, + 'unused_flag' ); }); }); @@ -2291,10 +2250,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 2400 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user142222' @@ -2321,10 +2277,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 4000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user142223' @@ -2351,10 +2304,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 6500 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user142224' @@ -2399,10 +2349,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 8000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' @@ -2447,10 +2394,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[18]), - 'BUCKETER: Assigned bucket 2400 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' @@ -2487,10 +2431,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 2400 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1111134' @@ -2518,10 +2459,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 4000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1111135' @@ -2549,10 +2487,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 6500 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1111136' @@ -2597,10 +2532,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 8000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' @@ -2645,10 +2577,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[18]), - 'BUCKETER: Assigned bucket 4000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 7c21034ad..7ce6a1c85 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LogHandler } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger' import { sprintf } from '../../utils/fns'; import fns from '../../utils/fns'; @@ -61,14 +61,15 @@ import { USER_NOT_IN_FORCED_VARIATION, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR, + FORCED_BUCKETING_FAILED, + BUCKETING_ID_NOT_STRING, } from '../../error_messages'; + import { AUDIENCE_EVALUATION_RESULT_COMBINED, - BUCKETING_ID_NOT_STRING, EVALUATING_AUDIENCES_COMBINED, EXPERIMENT_NOT_RUNNING, FEATURE_HAS_NO_EXPERIMENTS, - FORCED_BUCKETING_FAILED, NO_ROLLOUT_EXISTS, RETURNING_STORED_VARIATION, ROLLOUT_HAS_NO_EXPERIMENTS, @@ -106,7 +107,7 @@ export interface DecisionObj { interface DecisionServiceOptions { userProfileService: UserProfileService | null; - logger: LogHandler; + logger?: LoggerFacade; UNSTABLE_conditionEvaluators: unknown; } @@ -135,15 +136,15 @@ interface UserProfileTracker { * @returns {DecisionService} */ export class DecisionService { - private logger: LogHandler; + private logger?: LoggerFacade; private audienceEvaluator: AudienceEvaluator; private forcedVariationMap: { [key: string]: { [id: string]: string } }; private userProfileService: UserProfileService | null; constructor(options: DecisionServiceOptions) { - this.audienceEvaluator = createAudienceEvaluator(options.UNSTABLE_conditionEvaluators); - this.forcedVariationMap = {}; this.logger = options.logger; + this.audienceEvaluator = createAudienceEvaluator(options.UNSTABLE_conditionEvaluators, this.logger); + this.forcedVariationMap = {}; this.userProfileService = options.userProfileService || null; } @@ -170,7 +171,7 @@ export class DecisionService { const decideReasons: (string | number)[][] = []; const experimentKey = experiment.key; if (!this.checkIfExperimentIsActive(configObj, experimentKey)) { - this.logger.log(LOG_LEVEL.INFO, EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey); + this.logger?.info(EXPERIMENT_NOT_RUNNING, experimentKey); decideReasons.push([EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey]); return { result: null, @@ -202,10 +203,8 @@ export class DecisionService { if (!shouldIgnoreUPS) { variation = this.getStoredVariation(configObj, experiment, userId, userProfileTracker.userProfile); if (variation) { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( RETURNING_STORED_VARIATION, - MODULE_NAME, variation.key, experimentKey, userId, @@ -234,10 +233,8 @@ export class DecisionService { ); decideReasons.push(...decisionifUserIsInAudience.reasons); if (!decisionifUserIsInAudience.result) { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_NOT_IN_EXPERIMENT, - MODULE_NAME, userId, experimentKey, ); @@ -261,10 +258,8 @@ export class DecisionService { variation = configObj.variationIdMap[variationId]; } if (!variation) { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_HAS_NO_VARIATION, - MODULE_NAME, userId, experimentKey, ); @@ -280,10 +275,8 @@ export class DecisionService { }; } - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_HAS_VARIATION, - MODULE_NAME, userId, variation.key, experimentKey, @@ -381,10 +374,8 @@ export class DecisionService { if (experiment.forcedVariations && experiment.forcedVariations.hasOwnProperty(userId)) { const forcedVariationKey = experiment.forcedVariations[userId]; if (experiment.variationKeyMap.hasOwnProperty(forcedVariationKey)) { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_FORCED_IN_VARIATION, - MODULE_NAME, userId, forcedVariationKey, ); @@ -399,8 +390,7 @@ export class DecisionService { reasons: decideReasons, }; } else { - this.logger.log( - LOG_LEVEL.ERROR, + this.logger?.error( FORCED_BUCKETING_FAILED, MODULE_NAME, forcedVariationKey, @@ -446,10 +436,8 @@ export class DecisionService { const decideReasons: (string | number)[][] = []; const experimentAudienceConditions = getExperimentAudienceConditions(configObj, experiment.id); const audiencesById = getAudiencesById(configObj); - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( EVALUATING_AUDIENCES_COMBINED, - MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, JSON.stringify(experimentAudienceConditions), @@ -462,10 +450,8 @@ export class DecisionService { JSON.stringify(experimentAudienceConditions), ]); const result = this.audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, user); - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( AUDIENCE_EVALUATION_RESULT_COMBINED, - MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, result.toString().toUpperCase(), @@ -532,10 +518,9 @@ export class DecisionService { if (configObj.variationIdMap.hasOwnProperty(variationId)) { return configObj.variationIdMap[decision.variation_id]; } else { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( SAVED_VARIATION_NOT_FOUND, - MODULE_NAME, userId, + userId, variationId, experiment.key, ); @@ -563,10 +548,8 @@ export class DecisionService { try { return this.userProfileService.lookup(userId); } catch (ex: any) { - this.logger.log( - LOG_LEVEL.ERROR, + this.logger?.error( USER_PROFILE_LOOKUP_ERROR, - MODULE_NAME, userId, ex.message, ); @@ -613,14 +596,12 @@ export class DecisionService { experiment_bucket_map: userProfile, }); - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( SAVED_USER_VARIATION, - MODULE_NAME, userId, ); } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, USER_PROFILE_SAVE_ERROR, MODULE_NAME, userId, ex.message); + this.logger?.error(USER_PROFILE_SAVE_ERROR, userId, ex.message); } } @@ -671,10 +652,10 @@ export class DecisionService { const userId = user.getUserId(); if (rolloutDecision.variation) { - this.logger.log(LOG_LEVEL.DEBUG, USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key); + this.logger?.debug(USER_IN_ROLLOUT, userId, feature.key); decideReasons.push([USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); } else { - this.logger.log(LOG_LEVEL.DEBUG, USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key); + this.logger?.debug(USER_NOT_IN_ROLLOUT, userId, feature.key); decideReasons.push([USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); } @@ -759,7 +740,7 @@ export class DecisionService { } } } else { - this.logger.log(LOG_LEVEL.DEBUG, FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key); + this.logger?.debug(FEATURE_HAS_NO_EXPERIMENTS, feature.key); decideReasons.push([FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key]); } @@ -783,7 +764,7 @@ export class DecisionService { const decideReasons: (string | number)[][] = []; let decisionObj: DecisionObj; if (!feature.rolloutId) { - this.logger.log(LOG_LEVEL.DEBUG, NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key); + this.logger?.debug(NO_ROLLOUT_EXISTS, feature.key); decideReasons.push([NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key]); decisionObj = { experiment: null, @@ -799,10 +780,8 @@ export class DecisionService { const rollout = configObj.rolloutIdMap[feature.rolloutId]; if (!rollout) { - this.logger.log( - LOG_LEVEL.ERROR, + this.logger?.error( INVALID_ROLLOUT_ID, - MODULE_NAME, feature.rolloutId, feature.key, ); @@ -820,10 +799,8 @@ export class DecisionService { const rolloutRules = rollout.experiments; if (rolloutRules.length === 0) { - this.logger.log( - LOG_LEVEL.ERROR, + this.logger?.error( ROLLOUT_HAS_NO_EXPERIMENTS, - MODULE_NAME, feature.rolloutId, ); decideReasons.push([ROLLOUT_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.rolloutId]); @@ -892,9 +869,9 @@ export class DecisionService { ) { if (typeof attributes[CONTROL_ATTRIBUTES.BUCKETING_ID] === 'string') { bucketingId = String(attributes[CONTROL_ATTRIBUTES.BUCKETING_ID]); - this.logger.log(LOG_LEVEL.DEBUG, VALID_BUCKETING_ID, MODULE_NAME, bucketingId); + this.logger?.debug(VALID_BUCKETING_ID, bucketingId); } else { - this.logger.log(LOG_LEVEL.WARNING, BUCKETING_ID_NOT_STRING, MODULE_NAME); + this.logger?.warn(BUCKETING_ID_NOT_STRING); } } @@ -926,8 +903,7 @@ export class DecisionService { variation = getFlagVariationByKey(config, flagKey, variationKey); if (variation) { if (ruleKey) { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, flagKey, @@ -942,8 +918,7 @@ export class DecisionService { userId ]); } else { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, flagKey, @@ -958,8 +933,7 @@ export class DecisionService { } } else { if (ruleKey) { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, flagKey, ruleKey, @@ -972,8 +946,7 @@ export class DecisionService { userId ]); } else { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, flagKey, userId @@ -1007,10 +980,8 @@ export class DecisionService { if (this.forcedVariationMap.hasOwnProperty(userId)) { delete this.forcedVariationMap[userId][experimentId]; - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( VARIATION_REMOVED_FOR_USER, - MODULE_NAME, experimentKey, userId, ); @@ -1034,10 +1005,8 @@ export class DecisionService { this.forcedVariationMap[userId][experimentId] = variationId; } - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_MAPPED_TO_FORCED_VARIATION, - MODULE_NAME, variationId, experimentId, userId, @@ -1060,10 +1029,8 @@ export class DecisionService { const decideReasons: (string | number)[][] = []; const experimentToVariationMap = this.forcedVariationMap[userId]; if (!experimentToVariationMap) { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_HAS_NO_FORCED_VARIATION, - MODULE_NAME, userId, ); @@ -1080,8 +1047,7 @@ export class DecisionService { experimentId = experiment['id']; } else { // catching improperly formatted experiments - this.logger.log( - LOG_LEVEL.ERROR, + this.logger?.error( IMPROPERLY_FORMATTED_EXPERIMENT, MODULE_NAME, experimentKey, @@ -1099,7 +1065,7 @@ export class DecisionService { } } catch (ex: any) { // catching experiment not in datafile - this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.logger?.error(ex); decideReasons.push(ex.message); return { @@ -1110,8 +1076,7 @@ export class DecisionService { const variationId = experimentToVariationMap[experimentId]; if (!variationId) { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, MODULE_NAME, experimentKey, @@ -1125,10 +1090,8 @@ export class DecisionService { const variationKey = getVariationKeyFromId(configObj, variationId); if (variationKey) { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_HAS_FORCED_VARIATION, - MODULE_NAME, variationKey, experimentKey, userId, @@ -1141,10 +1104,8 @@ export class DecisionService { userId, ]); } else { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - MODULE_NAME, experimentKey, userId, ); @@ -1171,7 +1132,7 @@ export class DecisionService { variationKey: string | null ): boolean { if (variationKey != null && !stringValidator.validate(variationKey)) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_VARIATION_KEY, MODULE_NAME); + this.logger?.error(INVALID_VARIATION_KEY); return false; } @@ -1182,17 +1143,15 @@ export class DecisionService { experimentId = experiment['id']; } else { // catching improperly formatted experiments - this.logger.log( - LOG_LEVEL.ERROR, + this.logger?.error( IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, experimentKey, ); return false; } } catch (ex: any) { // catching experiment not in datafile - this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.logger?.error(ex); return false; } @@ -1201,7 +1160,7 @@ export class DecisionService { this.removeForcedVariation(userId, experimentId, experimentKey); return true; } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.logger?.error(ex); return false; } } @@ -1209,10 +1168,8 @@ export class DecisionService { const variationId = getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey); if (!variationId) { - this.logger.log( - LOG_LEVEL.ERROR, + this.logger?.error( NO_VARIATION_FOR_EXPERIMENT_KEY, - MODULE_NAME, variationKey, experimentKey, ); @@ -1223,7 +1180,7 @@ export class DecisionService { this.setInForcedVariationMap(userId, experimentId, variationId); return true; } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.logger?.error(ex); return false; } } @@ -1302,10 +1259,8 @@ export class DecisionService { ); decideReasons.push(...decisionifUserIsInAudience.reasons); if (decisionifUserIsInAudience.result) { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ); @@ -1324,10 +1279,8 @@ export class DecisionService { bucketedVariation = getVariationFromId(configObj, bucketerVariationId); } if (bucketedVariation) { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ); @@ -1338,10 +1291,8 @@ export class DecisionService { loggingKey]); } else if (!everyoneElse) { // skip this logging for EveryoneElse since this has a message not for EveryoneElse - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_NOT_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ); @@ -1356,10 +1307,8 @@ export class DecisionService { skipToEveryoneElse = true; } } else { - this.logger.log( - LOG_LEVEL.DEBUG, + this.logger?.debug( USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ); diff --git a/lib/modules/logging/index.ts b/lib/error/error_handler.ts similarity index 71% rename from lib/modules/logging/index.ts rename to lib/error/error_handler.ts index 47a1e99c8..4a772c71c 100644 --- a/lib/modules/logging/index.ts +++ b/lib/error/error_handler.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019, Optimizely + * Copyright 2019, 2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './errorHandler' -export * from './models' -export * from './logger' +/** + * @export + * @interface ErrorHandler + */ +export interface ErrorHandler { + /** + * @param {Error} exception + * @memberof ErrorHandler + */ + handleError(exception: Error): void +} diff --git a/lib/error/error_notifier.ts b/lib/error/error_notifier.ts new file mode 100644 index 000000000..6a00eaf1e --- /dev/null +++ b/lib/error/error_notifier.ts @@ -0,0 +1,32 @@ +import { MessageResolver } from "../message/message_resolver"; +import { sprintf } from "../utils/fns"; +import { ErrorHandler } from "./error_handler"; +import { OptimizelyError } from "./optimizly_error"; + +export interface ErrorNotifier { + notify(error: Error): void; + child(name: string): ErrorNotifier; +} + +export class DefaultErrorNotifier implements ErrorNotifier { + private name: string; + private errorHandler: ErrorHandler; + private messageResolver: MessageResolver; + + constructor(errorHandler: ErrorHandler, messageResolver: MessageResolver, name?: string) { + this.errorHandler = errorHandler; + this.messageResolver = messageResolver; + this.name = name || ''; + } + + notify(error: Error): void { + if (error instanceof OptimizelyError) { + error.setMessage(this.messageResolver); + } + this.errorHandler.handleError(error); + } + + child(name: string): ErrorNotifier { + return new DefaultErrorNotifier(this.errorHandler, this.messageResolver, name); + } +} diff --git a/lib/error/error_reporter.ts b/lib/error/error_reporter.ts new file mode 100644 index 000000000..9a9aa69d2 --- /dev/null +++ b/lib/error/error_reporter.ts @@ -0,0 +1,40 @@ +import { LoggerFacade } from "../logging/logger"; +import { ErrorNotifier } from "./error_notifier"; +import { OptimizelyError } from "./optimizly_error"; + +export class ErrorReporter { + private logger?: LoggerFacade; + private errorNotifier?: ErrorNotifier; + + constructor(logger?: LoggerFacade, errorNotifier?: ErrorNotifier) { + this.logger = logger; + this.errorNotifier = errorNotifier; + } + + report(error: Error): void; + report(baseMessage: string, ...params: any[]): void; + + report(error: Error | string, ...params: any[]): void { + if (typeof error === 'string') { + error = new OptimizelyError(error, ...params); + this.report(error); + return; + } + + if (this.errorNotifier) { + this.errorNotifier.notify(error); + } + + if (this.logger) { + this.logger.error(error); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + setErrorNotifier(errorNotifier: ErrorNotifier): void { + this.errorNotifier = errorNotifier; + } +} diff --git a/lib/error/optimizly_error.ts b/lib/error/optimizly_error.ts new file mode 100644 index 000000000..4c60a237b --- /dev/null +++ b/lib/error/optimizly_error.ts @@ -0,0 +1,32 @@ +import { MessageResolver } from "../message/message_resolver"; +import { sprintf } from "../utils/fns"; + +export class OptimizelyError extends Error { + private baseMessage: string; + private params: any[]; + private resolved = false; + constructor(baseMessage: string, ...params: any[]) { + super(); + this.name = 'OptimizelyError'; + this.baseMessage = baseMessage; + this.params = params; + } + + getMessage(resolver?: MessageResolver): string { + if (this.resolved) { + return this.message; + } + + if (resolver) { + this.setMessage(resolver); + return this.message; + } + + return this.baseMessage; + } + + setMessage(resolver: MessageResolver): void { + this.message = sprintf(resolver.resolve(this.baseMessage), ...this.params); + this.resolved = true; + } +} diff --git a/lib/error_messages.ts b/lib/error_messages.ts index 18d85ac13..75f869c2e 100644 --- a/lib/error_messages.ts +++ b/lib/error_messages.ts @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +export const NOTIFICATION_LISTENER_EXCEPTION = 'Notification listener for (%s) threw exception: %s'; export const BROWSER_ODP_MANAGER_INITIALIZATION_FAILED = '%s: Error initializing Browser ODP Manager.'; -export const CONDITION_EVALUATOR_ERROR = '%s: Error evaluating audience condition of type %s: %s'; +export const CONDITION_EVALUATOR_ERROR = 'Error evaluating audience condition of type %s: %s'; export const DATAFILE_AND_SDK_KEY_MISSING = '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely'; export const EXPERIMENT_KEY_NOT_IN_DATAFILE = '%s: Experiment key %s is not in datafile.'; -export const FEATURE_NOT_IN_DATAFILE = '%s: Feature key %s is not in datafile.'; +export const FEATURE_NOT_IN_DATAFILE = 'Feature key %s is not in datafile.'; export const FETCH_SEGMENTS_FAILED_NETWORK_ERROR = '%s: Audience segments fetch failed. (network error)'; export const FETCH_SEGMENTS_FAILED_DECODE_ERROR = '%s: Audience segments fetch failed. (decode error)'; -export const IMPROPERLY_FORMATTED_EXPERIMENT = '%s: Experiment key %s is improperly formatted.'; +export const IMPROPERLY_FORMATTED_EXPERIMENT = 'Experiment key %s is improperly formatted.'; export const INVALID_ATTRIBUTES = '%s: Provided attributes are in an invalid format.'; export const INVALID_BUCKETING_ID = '%s: Unable to generate hash for bucketing ID %s: %s'; export const INVALID_DATAFILE = '%s: Datafile is invalid - property %s: %s'; @@ -32,11 +33,11 @@ export const INVALID_ERROR_HANDLER = '%s: Provided "errorHandler" is in an inval export const INVALID_EVENT_DISPATCHER = '%s: Provided "eventDispatcher" is in an invalid format.'; export const INVALID_EVENT_TAGS = '%s: Provided event tags are in an invalid format.'; export const INVALID_EXPERIMENT_KEY = - '%s: Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; -export const INVALID_EXPERIMENT_ID = '%s: Experiment ID %s is not in datafile.'; + 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; +export const INVALID_EXPERIMENT_ID = 'Experiment ID %s is not in datafile.'; export const INVALID_GROUP_ID = '%s: Group ID %s is not in datafile.'; export const INVALID_LOGGER = '%s: Provided "logger" is in an invalid format.'; -export const INVALID_ROLLOUT_ID = '%s: Invalid rollout ID %s attached to feature %s'; +export const INVALID_ROLLOUT_ID = 'Invalid rollout ID %s attached to feature %s'; export const INVALID_USER_ID = '%s: Provided user ID is in an invalid format.'; export const INVALID_USER_PROFILE_SERVICE = '%s: Provided user profile service instance is in an invalid format: %s.'; export const LOCAL_STORAGE_DOES_NOT_EXIST = 'Error accessing window localStorage.'; @@ -45,7 +46,7 @@ export const MISSING_INTEGRATION_KEY = export const NO_DATAFILE_SPECIFIED = '%s: No datafile specified. Cannot start optimizely.'; export const NO_JSON_PROVIDED = '%s: No JSON object to validate against schema.'; export const NO_EVENT_PROCESSOR = 'No event processor is provided'; -export const NO_VARIATION_FOR_EXPERIMENT_KEY = '%s: No variation key %s defined in datafile for experiment %s.'; +export const NO_VARIATION_FOR_EXPERIMENT_KEY = 'No variation key %s defined in datafile for experiment %s.'; export const ODP_CONFIG_NOT_AVAILABLE = '%s: ODP is not integrated to the project.'; export const ODP_EVENT_FAILED = 'ODP event send failed.'; export const ODP_EVENT_MANAGER_IS_NOT_RUNNING = 'ODP event manager is not running.'; @@ -80,20 +81,20 @@ export const ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING = '%s: ODP register vuid failed. (Event Manager not instantiated).'; export const UNDEFINED_ATTRIBUTE = '%s: Provided attribute: %s has an undefined value.'; export const UNRECOGNIZED_ATTRIBUTE = - '%s: Unrecognized attribute %s provided. Pruning before sending event to Optimizely.'; -export const UNABLE_TO_CAST_VALUE = '%s: Unable to cast value %s to type %s, returning null.'; + 'Unrecognized attribute %s provided. Pruning before sending event to Optimizely.'; +export const UNABLE_TO_CAST_VALUE = 'Unable to cast value %s to type %s, returning null.'; export const USER_NOT_IN_FORCED_VARIATION = '%s: User %s is not in the forced variation map. Cannot remove their forced variation.'; -export const USER_PROFILE_LOOKUP_ERROR = '%s: Error while looking up user profile for user ID "%s": %s.'; -export const USER_PROFILE_SAVE_ERROR = '%s: Error while saving user profile for user ID "%s": %s.'; +export const USER_PROFILE_LOOKUP_ERROR = 'Error while looking up user profile for user ID "%s": %s.'; +export const USER_PROFILE_SAVE_ERROR = 'Error while saving user profile for user ID "%s": %s.'; export const VARIABLE_KEY_NOT_IN_DATAFILE = '%s: Variable with key "%s" associated with feature with key "%s" is not in datafile.'; export const VARIATION_ID_NOT_IN_DATAFILE = '%s: No variation ID %s defined in datafile for experiment %s.'; -export const VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT = '%s: Variation ID %s is not in the datafile.'; +export const VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT = 'Variation ID %s is not in the datafile.'; export const INVALID_INPUT_FORMAT = '%s: Provided %s is in an invalid format.'; export const INVALID_DATAFILE_VERSION = '%s: This version of the JavaScript SDK does not support the given datafile version: %s'; -export const INVALID_VARIATION_KEY = '%s: Provided variation key is in an invalid format.'; +export const INVALID_VARIATION_KEY = 'Provided variation key is in an invalid format.'; export const UNABLE_TO_GET_VUID = 'Unable to get VUID - ODP Manager is not instantiated yet.'; export const ERROR_FETCHING_DATAFILE = 'Error fetching datafile: %s'; export const DATAFILE_FETCH_REQUEST_FAILED = 'Datafile fetch request failed with status: %s'; @@ -102,6 +103,28 @@ export const EVENT_ACTION_INVALID = 'Event action invalid.'; export const FAILED_TO_SEND_ODP_EVENTS = 'failed to send odp events'; export const UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE = 'Unable to get VUID - VuidManager is not available' export const UNKNOWN_CONDITION_TYPE = - '%s: Audience condition %s has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.'; + 'Audience condition %s has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.'; export const UNKNOWN_MATCH_TYPE = - '%s: Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.'; + 'Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.'; +export const UNRECOGNIZED_DECIDE_OPTION = 'Unrecognized decide option %s provided.'; +export const INVALID_OBJECT = 'Optimizely object is not valid. Failing %s.'; +export const EVENT_KEY_NOT_FOUND = 'Event key %s is not in datafile.'; +export const NOT_TRACKING_USER = 'Not tracking user %s.'; +export const VARIABLE_REQUESTED_WITH_WRONG_TYPE = + 'Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.'; +export const UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX = + 'Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.'; +export const FORCED_BUCKETING_FAILED = 'Variation key %s is not in datafile. Not activating user %s.'; +export const BUCKETING_ID_NOT_STRING = 'BucketingID attribute is not a string. Defaulted to userId'; +export const UNEXPECTED_CONDITION_VALUE = + 'Audience condition %s evaluated to UNKNOWN because the condition value is not supported.'; +export const UNEXPECTED_TYPE = + 'Audience condition %s evaluated to UNKNOWN because a value of type "%s" was passed for user attribute "%s".'; +export const OUT_OF_BOUNDS = + 'Audience condition %s evaluated to UNKNOWN because the number value for user attribute "%s" is not in the range [-2^53, +2^53].'; +export const REQUEST_TIMEOUT = 'Request timeout'; +export const REQUEST_ERROR = 'Request error'; +export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; +export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; + +export const messages: string[] = []; diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index 4e955e364..3f8809d18 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -26,7 +26,7 @@ import { getMockLogger } from '../tests/mock/mock_logger'; import { getMockRepeater } from '../tests/mock/mock_repeater'; import * as retry from '../utils/executor/backoff_retry_runner'; import { ServiceState, StartupLog } from '../service'; -import { LogLevel } from '../modules/logging'; +import { LogLevel } from '../logging/logger'; const getMockDispatcher = () => { return { @@ -53,12 +53,12 @@ describe('QueueingEventProcessor', async () => { it('should log startupLogs on start', () => { const startupLogs: StartupLog[] = [ { - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'warn message', params: [1, 2] }, { - level: LogLevel.ERROR, + level: LogLevel.Error, message: 'error message', params: [3, 4] }, @@ -76,10 +76,10 @@ describe('QueueingEventProcessor', async () => { processor.setLogger(logger); processor.start(); - - expect(logger.log).toHaveBeenCalledTimes(2); - expect(logger.log).toHaveBeenNthCalledWith(1, LogLevel.WARNING, 'warn message', 1, 2); - expect(logger.log).toHaveBeenNthCalledWith(2, LogLevel.ERROR, 'error message', 3, 4); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('warn message', 1, 2); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith('error message', 3, 4); }); it('should resolve onRunning() when start() is called', async () => { diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index a487f6cdf..a6eee569c 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -19,7 +19,7 @@ import { Cache } from "../utils/cache/cache"; import { EventDispatcher, EventDispatcherResponse, LogEvent } from "./event_dispatcher/event_dispatcher"; import { buildLogEvent } from "./event_builder/log_event"; import { BackoffController, ExponentialBackoff, IntervalRepeater, Repeater } from "../utils/repeater/repeater"; -import { LoggerFacade } from "../modules/logging"; +import { LoggerFacade } from '../logging/logger'; import { BaseService, ServiceState, StartupLog } from "../service"; import { Consumer, Fn, Producer } from "../utils/type"; import { RunResult, runWithRetry } from "../utils/executor/backoff_retry_runner"; diff --git a/lib/event_processor/event_builder/user_event.ts b/lib/event_processor/event_builder/user_event.ts index 4db0aa8a4..970d12937 100644 --- a/lib/event_processor/event_builder/user_event.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -25,10 +25,8 @@ import { ProjectConfig, } from '../../project_config/project_config'; -import { getLogger } from '../../modules/logging'; import { UserAttributes } from '../../shared_types'; - -const logger = getLogger('EVENT_BUILDER'); +import { LoggerFacade } from '../../logging/logger'; export type VisitorAttribute = { entityId: string @@ -212,8 +210,8 @@ export const buildConversionEvent = function({ clientEngine, clientVersion, eventKey, - eventTags, -}: ConversionConfig): ConversionEvent { + eventTags, +}: ConversionConfig, logger?: LoggerFacade): ConversionEvent { const eventId = getEventId(configObj, eventKey); @@ -254,7 +252,8 @@ export const buildConversionEvent = function({ const buildVisitorAttributes = ( configObj: ProjectConfig, - attributes?: UserAttributes + attributes?: UserAttributes, + logger?: LoggerFacade ): VisitorAttribute[] => { const builtAttributes: VisitorAttribute[] = []; // Omit attribute values that are not supported by the log endpoint. diff --git a/lib/event_processor/event_processor.ts b/lib/event_processor/event_processor.ts index 2bc4d5be0..f33c1a7a1 100644 --- a/lib/event_processor/event_processor.ts +++ b/lib/event_processor/event_processor.ts @@ -15,7 +15,6 @@ */ import { ConversionEvent, ImpressionEvent } from './event_builder/user_event' import { LogEvent } from './event_dispatcher/event_dispatcher' -import { getLogger } from '../modules/logging' import { Service } from '../service' import { Consumer, Fn } from '../utils/type'; diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts index 938483f4f..49f96beed 100644 --- a/lib/event_processor/event_processor_factory.spec.ts +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -19,7 +19,7 @@ import { DEFAULT_EVENT_BATCH_SIZE, DEFAULT_EVENT_FLUSH_INTERVAL, getBatchEventPr import { BatchEventProcessor, BatchEventProcessorConfig, EventWithId,DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF } from './batch_event_processor'; import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; import { getMockSyncCache } from '../tests/mock/mock_cache'; -import { LogLevel } from '../modules/logging'; +import { LogLevel } from '../logging/logger'; vi.mock('./batch_event_processor'); vi.mock('../utils/repeater/repeater'); @@ -160,7 +160,7 @@ describe('getBatchEventProcessor', () => { const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'Invalid flushInterval %s, defaulting to %s', params: [undefined, DEFAULT_EVENT_FLUSH_INTERVAL], }])); @@ -181,7 +181,7 @@ describe('getBatchEventProcessor', () => { const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'Invalid flushInterval %s, defaulting to %s', params: [-1, DEFAULT_EVENT_FLUSH_INTERVAL], }])); @@ -217,7 +217,7 @@ describe('getBatchEventProcessor', () => { const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'Invalid batchSize %s, defaulting to %s', params: [undefined, DEFAULT_EVENT_BATCH_SIZE], }])); @@ -236,7 +236,7 @@ describe('getBatchEventProcessor', () => { const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'Invalid batchSize %s, defaulting to %s', params: [-1, DEFAULT_EVENT_BATCH_SIZE], }])); diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index f64143cf8..70f1b6310 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LogLevel } from "../common_exports"; +import { LogLevel } from "../logging/logger"; import { StartupLog } from "../service"; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; import { EventDispatcher } from "./event_dispatcher/event_dispatcher"; @@ -85,7 +85,7 @@ export const getBatchEventProcessor = ( let flushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; if (options.flushInterval === undefined || options.flushInterval <= 0) { startupLogs.push({ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'Invalid flushInterval %s, defaulting to %s', params: [options.flushInterval, DEFAULT_EVENT_FLUSH_INTERVAL], }); @@ -96,7 +96,7 @@ export const getBatchEventProcessor = ( let batchSize = DEFAULT_EVENT_BATCH_SIZE; if (options.batchSize === undefined || options.batchSize <= 0) { startupLogs.push({ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'Invalid batchSize %s, defaulting to %s', params: [options.batchSize, DEFAULT_EVENT_BATCH_SIZE], }); diff --git a/lib/exception_messages.ts b/lib/exception_messages.ts index 731607ff8..aa743b905 100644 --- a/lib/exception_messages.ts +++ b/lib/exception_messages.ts @@ -33,10 +33,6 @@ export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; export const FAILED_TO_STOP = 'Failed to stop'; export const YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE = 'You must provide at least one of sdkKey or datafile'; export const RETRY_CANCELLED = 'Retry cancelled'; -export const REQUEST_TIMEOUT = 'Request timeout'; -export const REQUEST_ERROR = 'Request error'; export const REQUEST_FAILED = 'Request failed'; -export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; -export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; export const PROMISE_SHOULD_NOT_HAVE_RESOLVED = 'Promise should not have resolved'; export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index b7bd5d0df..cc6c34cd0 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import logging, { getLogger } from './modules/logging/logger'; - import { assert } from 'chai'; import sinon from 'sinon'; import Optimizely from './optimizely'; @@ -63,6 +61,14 @@ const pause = timeoutMilliseconds => { return new Promise(resolve => setTimeout(resolve, timeoutMilliseconds)); }; +var getLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => getLogger(), +}) + describe('javascript-sdk (Browser)', function() { var clock; beforeEach(function() { @@ -76,61 +82,35 @@ describe('javascript-sdk (Browser)', function() { }); describe('APIs', function() { - it('should expose logger, errorHandler, eventDispatcher and enums', function() { - assert.isDefined(optimizelyFactory.logging); - assert.isDefined(optimizelyFactory.logging.createLogger); - assert.isDefined(optimizelyFactory.logging.createNoOpLogger); - assert.isDefined(optimizelyFactory.errorHandler); - assert.isDefined(optimizelyFactory.eventDispatcher); - assert.isDefined(optimizelyFactory.enums); - }); + // it('should expose logger, errorHandler, eventDispatcher and enums', function() { + // assert.isDefined(optimizelyFactory.logging); + // assert.isDefined(optimizelyFactory.logging.createLogger); + // assert.isDefined(optimizelyFactory.logging.createNoOpLogger); + // assert.isDefined(optimizelyFactory.errorHandler); + // assert.isDefined(optimizelyFactory.eventDispatcher); + // assert.isDefined(optimizelyFactory.enums); + // }); describe('createInstance', function() { var fakeErrorHandler = { handleError: function() {} }; var fakeEventDispatcher = { dispatchEvent: function() {} }; - var silentLogger; + var mockLogger; beforeEach(function() { - silentLogger = optimizelyFactory.logging.createLogger({ - logLevel: optimizelyFactory.enums.LOG_LEVEL.INFO, - logToConsole: false, - }); - sinon.spy(console, 'error'); - sinon.stub(configValidator, 'validate'); + mockLogger = getLogger(); + sinon.stub(mockLogger, 'error'); + sinon.stub(configValidator, 'validate'); global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); }); afterEach(function() { optimizelyFactory.__internalResetRetryState(); - console.error.restore(); + mockLogger.error.restore(); configValidator.validate.restore(); delete global.XMLHttpRequest; }); - // TODO: pending event handling will be done by EventProcessor instead - // describe('when an eventDispatcher is not passed in', function() { - // it('should wrap the default eventDispatcher and invoke sendPendingEvents', function() { - // var optlyInstance = optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager(), - // errorHandler: fakeErrorHandler, - // logger: silentLogger, - // }); - - // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - // }); - // }); - - describe('when an eventDispatcher is passed in', function() { - it('should NOT wrap the default eventDispatcher and invoke sendPendingEvents', function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - }); - }); // TODO: pending event handling should be part of the event processor // logic, not the dispatcher. Refactor accordingly. @@ -158,7 +138,7 @@ describe('javascript-sdk (Browser)', function() { assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), - logger: silentLogger, + logger: mockLogger, }); }); }); @@ -168,7 +148,7 @@ describe('javascript-sdk (Browser)', function() { projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, - logger: silentLogger, + logger: mockLogger, }); assert.instanceOf(optlyInstance, Optimizely); @@ -180,7 +160,7 @@ describe('javascript-sdk (Browser)', function() { projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, - logger: silentLogger, + logger: mockLogger, }); assert.equal('javascript-sdk', optlyInstance.clientEngine); @@ -193,7 +173,7 @@ describe('javascript-sdk (Browser)', function() { projectConfigManager: getMockProjectConfigManager(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, - logger: silentLogger, + logger: mockLogger, }); assert.equal('react-sdk', optlyInstance.clientEngine); }); @@ -205,7 +185,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var activate = optlyInstance.activate('testExperiment', 'testUser'); assert.strictEqual(activate, 'control'); @@ -218,7 +198,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -235,7 +215,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -258,7 +238,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -285,7 +265,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -315,7 +295,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -349,7 +329,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -372,7 +352,7 @@ describe('javascript-sdk (Browser)', function() { }), errorHandler: fakeErrorHandler, eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, + logger: mockLogger, }); var didSetVariation = optlyInstance.setForcedVariation( @@ -385,203 +365,6 @@ describe('javascript-sdk (Browser)', function() { var variation = optlyInstance.getVariation('testExperimentNotRunning', 'testUser'); assert.strictEqual(variation, null); }); - - describe('when passing in logLevel', function() { - beforeEach(function() { - sinon.stub(logging, 'setLogLevel'); - }); - - afterEach(function() { - logging.setLogLevel.restore(); - }); - - it('should call logging.setLogLevel', function() { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, - }); - sinon.assert.calledOnce(logging.setLogLevel); - sinon.assert.calledWithExactly(logging.setLogLevel, optimizelyFactory.enums.LOG_LEVEL.ERROR); - }); - }); - - describe('when passing in logger', function() { - beforeEach(function() { - sinon.stub(logging, 'setLogHandler'); - }); - - afterEach(function() { - logging.setLogHandler.restore(); - }); - - it('should call logging.setLogHandler with the supplied logger', function() { - var fakeLogger = { log: function() {} }; - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - logger: fakeLogger, - }); - sinon.assert.calledOnce(logging.setLogHandler); - sinon.assert.calledWithExactly(logging.setLogHandler, fakeLogger); - }); - }); - }); - - describe('ODP/ATS', () => { - var sandbox = sinon.sandbox.create(); - - const fakeOptimizely = { - onReady: () => Promise.resolve({ success: true }), - identifyUser: sinon.stub().returns(), - }; - - const fakeErrorHandler = { handleError: function() {} }; - const fakeEventDispatcher = { dispatchEvent: function() {} }; - let logger = getLogger(); - - const testFsUserId = 'fs_test_user'; - const testVuid = 'vuid_test_user'; - var clock; - const requestParams = new Map(); - const mockRequestHandler = { - makeRequest: (endpoint, headers, method, data) => { - requestParams.set('endpoint', endpoint); - requestParams.set('headers', headers); - requestParams.set('method', method); - requestParams.set('data', data); - return { - responsePromise: (async () => { - return { statusCode: 200 }; - })(), - }; - }, - args: requestParams, - }; - - beforeEach(function() { - sandbox.stub(logger, 'log'); - sandbox.stub(logger, 'error'); - sandbox.stub(logger, 'warn'); - clock = sinon.useFakeTimers(new Date()); - }); - - afterEach(function() { - sandbox.restore(); - clock.restore(); - requestParams.clear(); - }); - - - // TODO: these tests should be elsewhere - // it('should send an odp event when calling sendOdpEvent with valid parameters', async () => { - // const fakeEventManager = { - // updateSettings: sinon.spy(), - // start: sinon.spy(), - // stop: sinon.spy(), - // registerVuid: sinon.spy(), - // identifyUser: sinon.spy(), - // sendEvent: sinon.spy(), - // flush: sinon.spy(), - // }; - - // const config = createProjectConfig(testData.getOdpIntegratedConfigWithoutSegments()); - // const projectConfigManager = getMockProjectConfigManager({ - // initConfig: config, - // onRunning: Promise.resolve(), - // }); - - // const client = optimizelyFactory.createInstance({ - // projectConfigManager, - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // eventBatchSize: null, - // logger, - // odpOptions: { - // eventManager: fakeEventManager, - // }, - // }); - - // projectConfigManager.pushUpdate(config); - // await client.onReady(); - - // client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); - - // sinon.assert.notCalled(logger.error); - // sinon.assert.called(fakeEventManager.sendEvent); - // }); - - - // it('should log an error when attempting to send an odp event when odp is disabled', async () => { - // const config = createProjectConfig(testData.getTestProjectConfigWithFeatures()); - // const projectConfigManager = getMockProjectConfigManager({ - // initConfig: config, - // onRunning: Promise.resolve(), - // }); - - // const client = optimizelyFactory.createInstance({ - // projectConfigManager, - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // eventBatchSize: null, - // logger, - // odpOptions: { - // disabled: true, - // }, - // }); - - // projectConfigManager.pushUpdate(config); - - // await client.onReady(); - - // assert.isUndefined(client.odpManager); - // sinon.assert.calledWith(logger.log, optimizelyFactory.enums.LOG_LEVEL.INFO, 'ODP Disabled.'); - - // client.sendOdpEvent(ODP_EVENT_ACTION.INITIALIZED); - - // sinon.assert.calledWith( - // logger.error, - // optimizelyFactory.enums.ERROR_MESSAGES.ODP_EVENT_FAILED_ODP_MANAGER_MISSING - // ); - // }); - - // it('should send odp client_initialized on client instantiation', async () => { - // const odpConfig = new OdpConfig('key', 'host', 'pixel', []); - // const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); - // sinon.spy(apiManager, 'sendEvents'); - // const eventManager = new BrowserOdpEventManager({ - // odpConfig, - // apiManager, - // logger, - // }); - // const datafile = testData.getOdpIntegratedConfigWithSegments(); - // const config = createProjectConfig(datafile); - // const projectConfigManager = getMockProjectConfigManager({ - // initConfig: config, - // onRunning: Promise.resolve(), - // }); - - // const client = optimizelyFactory.createInstance({ - // projectConfigManager, - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // eventBatchSize: null, - // logger, - // odpOptions: { - // odpConfig, - // eventManager, - // }, - // }); - - // projectConfigManager.pushUpdate(config); - // await client.onReady(); - - // clock.tick(100); - - // const [_, events] = apiManager.sendEvents.getCall(0).args; - - // const [firstEvent] = events; - // assert.equal(firstEvent.action, 'client_initialized'); - // assert.equal(firstEvent.type, 'fullstack'); - // }); }); }); }); diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 681c281c7..4834a09c8 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -14,14 +14,11 @@ * limitations under the License. */ -import logHelper from './modules/logging/logger'; -import { getLogger, setErrorHandler, getErrorHandler, LogLevel } from './modules/logging'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; -import * as loggerPlugin from './plugins/logger'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; import Optimizely from './optimizely'; @@ -35,9 +32,6 @@ import { createVuidManager } from './vuid/vuid_manager_factory.browser'; import { createOdpManager } from './odp/odp_manager_factory.browser'; import { ODP_DISABLED, UNABLE_TO_ATTACH_UNLOAD } from './log_messages'; -const logger = getLogger(); -logHelper.setLogHandler(loggerPlugin.createLogger()); -logHelper.setLogLevel(LogLevel.INFO); const MODULE_NAME = 'INDEX_BROWSER'; const DEFAULT_EVENT_BATCH_SIZE = 10; @@ -54,31 +48,7 @@ let hasRetriedEvents = false; */ const createInstance = function(config: Config): Client | null { try { - // TODO warn about setting per instance errorHandler / logger / logLevel - let isValidInstance = false; - - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - logHelper.setLogHandler(config.logger); - // respect the logger's shouldLog functionality - logHelper.setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - logHelper.setLogLevel(config.logLevel); - } - - try { - configValidator.validate(config); - isValidInstance = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex) { - logger.error(ex); - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); + configValidator.validate(config); const { clientEngine, clientVersion } = config; @@ -86,10 +56,6 @@ const createInstance = function(config: Config): Client | null { ...config, clientEngine: clientEngine || enums.JAVASCRIPT_CLIENT_ENGINE, clientVersion: clientVersion || enums.CLIENT_VERSION, - logger, - errorHandler, - notificationCenter, - isValidInstance, }; const optimizely = new Optimizely(optimizelyOptions); @@ -107,13 +73,13 @@ const createInstance = function(config: Config): Client | null { } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - logger.error(UNABLE_TO_ATTACH_UNLOAD, MODULE_NAME, e.message); + config.logger?.error(UNABLE_TO_ATTACH_UNLOAD, e.message); } return optimizely; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - logger.error(e); + config.logger?.error(e); return null; } }; @@ -122,21 +88,11 @@ const __internalResetRetryState = function(): void { hasRetriedEvents = false; }; -/** - * Entry point into the Optimizely Browser SDK - */ - -const setLogHandler = logHelper.setLogHandler; -const setLogLevel = logHelper.setLogLevel; - export { - loggerPlugin as logging, defaultErrorHandler as errorHandler, defaultEventDispatcher as eventDispatcher, sendBeaconEventDispatcher, enums, - setLogHandler as setLogger, - setLogLevel, createInstance, __internalResetRetryState, OptimizelyDecideOption, @@ -153,13 +109,10 @@ export * from './common_exports'; export default { ...commonExports, - logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, sendBeaconEventDispatcher, enums, - setLogger: setLogHandler, - setLogLevel, createInstance, __internalResetRetryState, OptimizelyDecideOption, diff --git a/lib/index.lite.tests.js b/lib/index.lite.tests.js deleted file mode 100644 index 729af3b19..000000000 --- a/lib/index.lite.tests.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2021-2024 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; -import sinon from 'sinon'; - -import * as enums from './utils/enums'; -import Optimizely from './optimizely'; -import * as loggerPlugin from './plugins/logger'; -import optimizelyFactory from './index.lite'; -import configValidator from './utils/config_validator'; -import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; -import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; - -describe('optimizelyFactory', function() { - describe('APIs', function() { - it('should expose logger, errorHandler, eventDispatcher and enums', function() { - assert.isDefined(optimizelyFactory.logging); - assert.isDefined(optimizelyFactory.logging.createLogger); - assert.isDefined(optimizelyFactory.logging.createNoOpLogger); - assert.isDefined(optimizelyFactory.errorHandler); - assert.isDefined(optimizelyFactory.enums); - }); - - describe('createInstance', function() { - var fakeErrorHandler = { handleError: function() {} }; - var fakeEventDispatcher = { dispatchEvent: function() {} }; - var fakeLogger; - - beforeEach(function() { - fakeLogger = { log: sinon.spy(), setLogLevel: sinon.spy() }; - sinon.stub(loggerPlugin, 'createLogger').returns(fakeLogger); - sinon.stub(configValidator, 'validate'); - sinon.stub(console, 'error'); - }); - - afterEach(function() { - loggerPlugin.createLogger.restore(); - configValidator.validate.restore(); - console.error.restore(); - }); - - it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { - configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); - var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); - assert.doesNotThrow(function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - logger: localLogger, - }); - }); - sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); - }); - - it('should create an instance of optimizely', function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); - - assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.4'); - }); - }); - }); -}); diff --git a/lib/index.lite.ts b/lib/index.lite.ts index eae2a00e0..0e00e33d4 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -13,27 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, - setLogHandler, - setLogLevel - } from './modules/logging'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import * as enums from './utils/enums'; -import * as loggerPlugin from './plugins/logger'; import Optimizely from './optimizely'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; import * as commonExports from './common_exports'; -const logger = getLogger(); -setLogHandler(loggerPlugin.createLogger()); -setLogLevel(LogLevel.ERROR); - /** * Creates an instance of the Optimizely class * @param {ConfigLite} config @@ -42,55 +29,24 @@ setLogLevel(LogLevel.ERROR); */ const createInstance = function(config: Config): Client | null { try { - - // TODO warn about setting per instance errorHandler / logger / logLevel - let isValidInstance = false; - - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - setLogHandler(config.logger); - // respect the logger's shouldLog functionality - setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - setLogLevel(config.logLevel); - } - - try { - configValidator.validate(config); - isValidInstance = true; - } catch (ex: any) { - logger.error(ex); - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - + configValidator.validate(config); + const optimizelyOptions = { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, - logger, - errorHandler, - notificationCenter, - isValidInstance: isValidInstance, }; const optimizely = new Optimizely(optimizelyOptions); return optimizely; } catch (e: any) { - logger.error(e); + config.logger?.error(e); return null; } }; export { - loggerPlugin as logging, defaultErrorHandler as errorHandler, enums, - setLogHandler as setLogger, - setLogLevel, createInstance, OptimizelyDecideOption, }; @@ -99,11 +55,8 @@ export * from './common_exports'; export default { ...commonExports, - logging: loggerPlugin, errorHandler: defaultErrorHandler, enums, - setLogger: setLogHandler, - setLogLevel, createInstance, OptimizelyDecideOption, }; diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index ee4cf1766..891edc137 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -16,42 +16,45 @@ import { assert } from 'chai'; import sinon from 'sinon'; -import * as enums from './utils/enums'; import Optimizely from './optimizely'; import testData from './tests/test_data'; -import * as loggerPlugin from './plugins/logger'; import optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('optimizelyFactory', function() { describe('APIs', function() { - it('should expose logger, errorHandler, eventDispatcher and enums', function() { - assert.isDefined(optimizelyFactory.logging); - assert.isDefined(optimizelyFactory.logging.createLogger); - assert.isDefined(optimizelyFactory.logging.createNoOpLogger); - assert.isDefined(optimizelyFactory.errorHandler); - assert.isDefined(optimizelyFactory.eventDispatcher); - assert.isDefined(optimizelyFactory.enums); - }); + // it('should expose logger, errorHandler, eventDispatcher and enums', function() { + // assert.isDefined(optimizelyFactory.logging); + // assert.isDefined(optimizelyFactory.logging.createLogger); + // assert.isDefined(optimizelyFactory.logging.createNoOpLogger); + // assert.isDefined(optimizelyFactory.errorHandler); + // assert.isDefined(optimizelyFactory.eventDispatcher); + // assert.isDefined(optimizelyFactory.enums); + // }); describe('createInstance', function() { var fakeErrorHandler = { handleError: function() {} }; var fakeEventDispatcher = { dispatchEvent: function() {} }; - var fakeLogger; + var fakeLogger = createLogger(); beforeEach(function() { - fakeLogger = { log: sinon.spy(), setLogLevel: sinon.spy() }; - sinon.stub(loggerPlugin, 'createLogger').returns(fakeLogger); sinon.stub(configValidator, 'validate'); - sinon.stub(console, 'error'); + sinon.stub(fakeLogger, 'error'); }); afterEach(function() { - loggerPlugin.createLogger.restore(); configValidator.validate.restore(); - console.error.restore(); + fakeLogger.error.restore(); }); // it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { @@ -71,22 +74,23 @@ describe('optimizelyFactory', function() { assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), + logger: fakeLogger, }); }); - sinon.assert.calledOnce(console.error); + sinon.assert.calledOnce(fakeLogger.error); }); - it('should create an instance of optimizely', function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); + // it('should create an instance of optimizely', function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager(), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // }); - assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.4'); - }); + // assert.instanceOf(optlyInstance, Optimizely); + // assert.equal(optlyInstance.clientVersion, '5.3.4'); + // }); // TODO: user will create and inject an event processor // these tests will be refactored accordingly // describe('event processor configuration', function() { diff --git a/lib/index.node.ts b/lib/index.node.ts index 156b06adf..995510baa 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -14,10 +14,9 @@ * limitations under the License. */ -import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; +// import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; import Optimizely from './optimizely'; import * as enums from './utils/enums'; -import * as loggerPlugin from './plugins/logger'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; @@ -29,9 +28,7 @@ import { createForwardingEventProcessor, createBatchEventProcessor } from './eve import { createVuidManager } from './vuid/vuid_manager_factory.node'; import { createOdpManager } from './odp/odp_manager_factory.node'; import { ODP_DISABLED } from './log_messages'; - -const logger = getLogger(); -setLogLevel(LogLevel.ERROR); +import { create } from 'domain'; const DEFAULT_EVENT_BATCH_SIZE = 10; const DEFAULT_EVENT_FLUSH_INTERVAL = 30000; // Unit is ms, default is 30s @@ -45,37 +42,7 @@ const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; */ const createInstance = function(config: Config): Client | null { try { - let hasLogger = false; - let isValidInstance = false; - - // TODO warn about setting per instance errorHandler / logger / logLevel - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - // only set a logger in node if one is provided, by not setting we are noop-ing - hasLogger = true; - setLogHandler(config.logger); - // respect the logger's shouldLog functionality - setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - setLogLevel(config.logLevel); - } - try { - configValidator.validate(config); - isValidInstance = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex) { - if (hasLogger) { - logger.error(ex); - } else { - console.error(ex.message); - } - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); + configValidator.validate(config); const { clientEngine, clientVersion } = config; @@ -83,16 +50,12 @@ const createInstance = function(config: Config): Client | null { ...config, clientEngine: clientEngine || enums.NODE_CLIENT_ENGINE, clientVersion: clientVersion || enums.CLIENT_VERSION, - logger, - errorHandler, - notificationCenter, - isValidInstance, }; return new Optimizely(optimizelyOptions); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - logger.error(e); + config.logger?.error(e); return null; } }; @@ -101,12 +64,9 @@ const createInstance = function(config: Config): Client | null { * Entry point into the Optimizely Node testing SDK */ export { - loggerPlugin as logging, defaultErrorHandler as errorHandler, defaultEventDispatcher as eventDispatcher, enums, - setLogHandler as setLogger, - setLogLevel, createInstance, OptimizelyDecideOption, createPollingProjectConfigManager, @@ -120,12 +80,9 @@ export * from './common_exports'; export default { ...commonExports, - logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, enums, - setLogger: setLogHandler, - setLogLevel, createInstance, OptimizelyDecideOption, createPollingProjectConfigManager, diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index 64ca63520..c61e9cf37 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -15,8 +15,6 @@ */ import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; -import * as logging from './modules/logging/logger'; - import Optimizely from './optimizely'; import testData from './tests/test_data'; import packageJSON from '../package.json'; @@ -24,6 +22,7 @@ import optimizelyFactory from './index.react_native'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; +import { getMockLogger } from './tests/mock/mock_logger'; vi.mock('@react-native-community/netinfo'); vi.mock('react-native-get-random-values') @@ -41,9 +40,6 @@ describe('javascript-sdk/react-native', () => { describe('APIs', () => { it('should expose logger, errorHandler, eventDispatcher and enums', () => { - expect(optimizelyFactory.logging).toBeDefined(); - expect(optimizelyFactory.logging.createLogger).toBeDefined(); - expect(optimizelyFactory.logging.createNoOpLogger).toBeDefined(); expect(optimizelyFactory.errorHandler).toBeDefined(); expect(optimizelyFactory.eventDispatcher).toBeDefined(); expect(optimizelyFactory.enums).toBeDefined(); @@ -56,16 +52,13 @@ describe('javascript-sdk/react-native', () => { } }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - let silentLogger; + let mockLogger; beforeEach(() => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - silentLogger = optimizelyFactory.logging.createLogger(); + mockLogger = getMockLogger(); vi.spyOn(console, 'error'); - vi.spyOn(configValidator, 'validate').mockImplementation(() => { - throw new Error('Invalid config or something'); - }); }); afterEach(() => { @@ -73,12 +66,15 @@ describe('javascript-sdk/react-native', () => { }); it('should not throw if the provided config is not valid', () => { + vi.spyOn(configValidator, 'validate').mockImplementation(() => { + throw new Error('Invalid config or something'); + }); expect(function() { const optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - logger: silentLogger, + logger: mockLogger, }); }).not.toThrow(); }); @@ -89,7 +85,7 @@ describe('javascript-sdk/react-native', () => { errorHandler: fakeErrorHandler, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - logger: silentLogger, + logger: mockLogger, }); expect(optlyInstance).toBeInstanceOf(Optimizely); @@ -104,7 +100,7 @@ describe('javascript-sdk/react-native', () => { errorHandler: fakeErrorHandler, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - logger: silentLogger, + logger: mockLogger, }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -114,64 +110,64 @@ describe('javascript-sdk/react-native', () => { expect(packageJSON.version).toEqual(optlyInstance.clientVersion); }); - it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { - const optlyInstance = optimizelyFactory.createInstance({ - clientEngine: 'react-sdk', - projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - logger: silentLogger, - }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect('react-native-sdk').toEqual(optlyInstance.clientEngine); - }); - - describe('when passing in logLevel', () => { - beforeEach(() => { - vi.spyOn(logging, 'setLogLevel'); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should call logging.setLogLevel', () => { - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, - }); - expect(logging.setLogLevel).toBeCalledTimes(1); - expect(logging.setLogLevel).toBeCalledWith(optimizelyFactory.enums.LOG_LEVEL.ERROR); - }); - }); - - describe('when passing in logger', () => { - beforeEach(() => { - vi.spyOn(logging, 'setLogHandler'); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should call logging.setLogHandler with the supplied logger', () => { - const fakeLogger = { log: function() {} }; - optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - logger: fakeLogger, - }); - expect(logging.setLogHandler).toBeCalledTimes(1); - expect(logging.setLogHandler).toBeCalledWith(fakeLogger); - }); - }); + // it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { + // const optlyInstance = optimizelyFactory.createInstance({ + // clientEngine: 'react-sdk', + // projectConfigManager: getMockProjectConfigManager(), + // errorHandler: fakeErrorHandler, + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // logger: mockLogger, + // }); + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // expect('react-native-sdk').toEqual(optlyInstance.clientEngine); + // }); + + // describe('when passing in logLevel', () => { + // beforeEach(() => { + // vi.spyOn(logging, 'setLogLevel'); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should call logging.setLogLevel', () => { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfig()), + // }), + // logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, + // }); + // expect(logging.setLogLevel).toBeCalledTimes(1); + // expect(logging.setLogLevel).toBeCalledWith(optimizelyFactory.enums.LOG_LEVEL.ERROR); + // }); + // }); + + // describe('when passing in logger', () => { + // beforeEach(() => { + // vi.spyOn(logging, 'setLogHandler'); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should call logging.setLogHandler with the supplied logger', () => { + // const fakeLogger = { log: function() {} }; + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfig()), + // }), + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // logger: fakeLogger, + // }); + // expect(logging.setLogHandler).toBeCalledTimes(1); + // expect(logging.setLogHandler).toBeCalledWith(fakeLogger); + // }); + // }); }); }); }); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 565ad0605..a7bc5853f 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; import * as enums from './utils/enums'; import Optimizely from './optimizely'; import configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; -import * as loggerPlugin from './plugins/logger/index.react_native'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; @@ -31,11 +29,6 @@ import { createVuidManager } from './vuid/vuid_manager_factory.react_native'; import 'fast-text-encoding'; import 'react-native-get-random-values'; -import { ODP_DISABLED } from './log_messages'; - -const logger = getLogger(); -setLogHandler(loggerPlugin.createLogger()); -setLogLevel(LogLevel.INFO); const DEFAULT_EVENT_BATCH_SIZE = 10; const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s @@ -49,31 +42,7 @@ const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; */ const createInstance = function(config: Config): Client | null { try { - // TODO warn about setting per instance errorHandler / logger / logLevel - let isValidInstance = false; - - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - setLogHandler(config.logger); - // respect the logger's shouldLog functionality - setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - setLogLevel(config.logLevel); - } - - try { - configValidator.validate(config); - isValidInstance = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex) { - logger.error(ex); - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); + configValidator.validate(config); const { clientEngine, clientVersion } = config; @@ -81,10 +50,6 @@ const createInstance = function(config: Config): Client | null { ...config, clientEngine: clientEngine || enums.REACT_NATIVE_JS_CLIENT_ENGINE, clientVersion: clientVersion || enums.CLIENT_VERSION, - logger, - errorHandler, - notificationCenter, - isValidInstance: isValidInstance, }; // If client engine is react, convert it to react native. @@ -95,7 +60,7 @@ const createInstance = function(config: Config): Client | null { return new Optimizely(optimizelyOptions); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - logger.error(e); + config.logger?.error(e); return null; } }; @@ -104,12 +69,9 @@ const createInstance = function(config: Config): Client | null { * Entry point into the Optimizely Javascript SDK for React Native */ export { - loggerPlugin as logging, defaultErrorHandler as errorHandler, defaultEventDispatcher as eventDispatcher, enums, - setLogHandler as setLogger, - setLogLevel, createInstance, OptimizelyDecideOption, createPollingProjectConfigManager, @@ -123,12 +85,9 @@ export * from './common_exports'; export default { ...commonExports, - logging: loggerPlugin, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, enums, - setLogger: setLogHandler, - setLogLevel, createInstance, OptimizelyDecideOption, createPollingProjectConfigManager, diff --git a/lib/log_messages.ts b/lib/log_messages.ts index 4c2ab6e40..d5830cba7 100644 --- a/lib/log_messages.ts +++ b/lib/log_messages.ts @@ -18,56 +18,50 @@ export const ACTIVATE_USER = '%s: Activating user %s in experiment %s.'; export const DISPATCH_CONVERSION_EVENT = '%s: Dispatching conversion event to URL %s with params %s.'; export const DISPATCH_IMPRESSION_EVENT = '%s: Dispatching impression event to URL %s with params %s.'; export const DEPRECATED_EVENT_VALUE = '%s: Event value is deprecated in %s call.'; -export const EVENT_KEY_NOT_FOUND = '%s: Event key %s is not in datafile.'; -export const EXPERIMENT_NOT_RUNNING = '%s: Experiment %s is not running.'; -export const FEATURE_ENABLED_FOR_USER = '%s: Feature %s is enabled for user %s.'; -export const FEATURE_NOT_ENABLED_FOR_USER = '%s: Feature %s is not enabled for user %s.'; -export const FEATURE_HAS_NO_EXPERIMENTS = '%s: Feature %s is not attached to any experiments.'; +export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; +export const FEATURE_ENABLED_FOR_USER = 'Feature %s is enabled for user %s.'; +export const FEATURE_NOT_ENABLED_FOR_USER = 'Feature %s is not enabled for user %s.'; +export const FEATURE_HAS_NO_EXPERIMENTS = 'Feature %s is not attached to any experiments.'; export const FAILED_TO_PARSE_VALUE = '%s: Failed to parse event value "%s" from event tags.'; -export const FAILED_TO_PARSE_REVENUE = '%s: Failed to parse revenue value "%s" from event tags.'; -export const FORCED_BUCKETING_FAILED = '%s: Variation key %s is not in datafile. Not activating user %s.'; -export const INVALID_OBJECT = '%s: Optimizely object is not valid. Failing %s.'; -export const INVALID_CLIENT_ENGINE = '%s: Invalid client engine passed: %s. Defaulting to node-sdk.'; +export const FAILED_TO_PARSE_REVENUE = 'Failed to parse revenue value "%s" from event tags.'; +export const INVALID_CLIENT_ENGINE = 'Invalid client engine passed: %s. Defaulting to node-sdk.'; export const INVALID_DEFAULT_DECIDE_OPTIONS = '%s: Provided default decide options is not an array.'; -export const INVALID_DECIDE_OPTIONS = '%s: Provided decide options is not an array. Using default decide options.'; -export const NOTIFICATION_LISTENER_EXCEPTION = '%s: Notification listener for (%s) threw exception: %s'; -export const NO_ROLLOUT_EXISTS = '%s: There is no rollout of feature %s.'; -export const NOT_ACTIVATING_USER = '%s: Not activating user %s for experiment %s.'; -export const NOT_TRACKING_USER = '% s: Not tracking user %s.'; +export const INVALID_DECIDE_OPTIONS = 'Provided decide options is not an array. Using default decide options.'; +export const NO_ROLLOUT_EXISTS = 'There is no rollout of feature %s.'; +export const NOT_ACTIVATING_USER = 'Not activating user %s for experiment %s.'; export const ODP_DISABLED = 'ODP Disabled.'; export const ODP_IDENTIFY_FAILED_ODP_DISABLED = '%s: ODP identify event for user %s is not dispatched (ODP disabled).'; export const ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED = '%s: ODP identify event %s is not dispatched (ODP not integrated).'; export const ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED = '%s: sendOdpEvent failed to parse through and convert fs_user_id aliases'; -export const PARSED_REVENUE_VALUE = '%s: Parsed revenue value "%s" from event tags.'; -export const PARSED_NUMERIC_VALUE = '%s: Parsed event value "%s" from event tags.'; +export const PARSED_REVENUE_VALUE = 'Parsed revenue value "%s" from event tags.'; +export const PARSED_NUMERIC_VALUE = 'Parsed event value "%s" from event tags.'; export const RETURNING_STORED_VARIATION = - '%s: Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; -export const ROLLOUT_HAS_NO_EXPERIMENTS = '%s: Rollout of feature %s has no experiments'; -export const SAVED_USER_VARIATION = '%s: Saved user profile for user "%s".'; + 'Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; +export const ROLLOUT_HAS_NO_EXPERIMENTS = 'Rollout of feature %s has no experiments'; +export const SAVED_USER_VARIATION = 'Saved user profile for user "%s".'; export const UPDATED_USER_VARIATION = '%s: Updated variation "%s" of experiment "%s" for user "%s".'; export const SAVED_VARIATION_NOT_FOUND = - '%s: User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.'; -export const SHOULD_NOT_DISPATCH_ACTIVATE = '%s: Experiment %s is not in "Running" state. Not activating user.'; -export const SKIPPING_JSON_VALIDATION = '%s: Skipping JSON schema validation.'; -export const TRACK_EVENT = '%s: Tracking event %s for user %s.'; -export const UNRECOGNIZED_DECIDE_OPTION = '%s: Unrecognized decide option %s provided.'; -export const USER_BUCKETED_INTO_TARGETING_RULE = '%s: User %s bucketed into targeting rule %s.'; + 'User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.'; +export const SHOULD_NOT_DISPATCH_ACTIVATE = 'Experiment %s is not in "Running" state. Not activating user.'; +export const SKIPPING_JSON_VALIDATION = 'Skipping JSON schema validation.'; +export const TRACK_EVENT = 'Tracking event %s for user %s.'; +export const USER_BUCKETED_INTO_TARGETING_RULE = 'User %s bucketed into targeting rule %s.'; export const USER_IN_FEATURE_EXPERIMENT = '%s: User %s is in variation %s of experiment %s on the feature %s.'; -export const USER_IN_ROLLOUT = '%s: User %s is in rollout of feature %s.'; +export const USER_IN_ROLLOUT = 'User %s is in rollout of feature %s.'; export const USER_NOT_BUCKETED_INTO_EVERYONE_TARGETING_RULE = '%s: User %s not bucketed into everyone targeting rule due to traffic allocation.'; export const USER_NOT_BUCKETED_INTO_ANY_EXPERIMENT_IN_GROUP = '%s: User %s is not in any experiment of group %s.'; export const USER_NOT_BUCKETED_INTO_TARGETING_RULE = - '%s User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; -export const USER_FORCED_IN_VARIATION = '%s: User %s is forced in variation %s.'; + 'User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; +export const USER_FORCED_IN_VARIATION = 'User %s is forced in variation %s.'; export const USER_MAPPED_TO_FORCED_VARIATION = - '%s: Set variation %s for experiment %s and user %s in the forced variation map.'; + 'Set variation %s for experiment %s and user %s in the forced variation map.'; export const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = - '%s: User %s does not meet conditions for targeting rule %s.'; -export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = '%s: User %s meets conditions for targeting rule %s.'; -export const USER_HAS_VARIATION = '%s: User %s is in variation %s of experiment %s.'; + 'User %s does not meet conditions for targeting rule %s.'; +export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = 'User %s meets conditions for targeting rule %s.'; +export const USER_HAS_VARIATION = 'User %s is in variation %s of experiment %s.'; export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = @@ -77,46 +71,39 @@ export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; export const USER_HAS_FORCED_VARIATION = - '%s: Variation %s is mapped to experiment %s and user %s in the forced variation map.'; -export const USER_HAS_NO_VARIATION = '%s: User %s is in no variation of experiment %s.'; -export const USER_HAS_NO_FORCED_VARIATION = '%s: User %s is not in the forced variation map.'; + 'Variation %s is mapped to experiment %s and user %s in the forced variation map.'; +export const USER_HAS_NO_VARIATION = 'User %s is in no variation of experiment %s.'; +export const USER_HAS_NO_FORCED_VARIATION = 'User %s is not in the forced variation map.'; export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = - '%s: No experiment %s mapped to user %s in the forced variation map.'; -export const USER_NOT_IN_EXPERIMENT = '%s: User %s does not meet conditions to be in experiment %s.'; -export const USER_NOT_IN_ROLLOUT = '%s: User %s is not in rollout of feature %s.'; + 'No experiment %s mapped to user %s in the forced variation map.'; +export const USER_NOT_IN_EXPERIMENT = 'User %s does not meet conditions to be in experiment %s.'; +export const USER_NOT_IN_ROLLOUT = 'User %s is not in rollout of feature %s.'; export const USER_RECEIVED_DEFAULT_VARIABLE_VALUE = - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".'; + 'User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".'; export const FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE = - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".'; + 'Feature "%s" is not enabled for user %s. Returning the default variable value "%s".'; export const VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE = - '%s: Variable "%s" is not used in variation "%s". Returning default value.'; -export const USER_RECEIVED_VARIABLE_VALUE = '%s: Got variable value "%s" for variable "%s" of feature flag "%s"'; -export const VALID_DATAFILE = '%s: Datafile is valid.'; -export const VALID_USER_PROFILE_SERVICE = '%s: Valid user profile service provided.'; -export const VARIATION_REMOVED_FOR_USER = '%s: Variation mapped to experiment %s has been removed for user %s.'; -export const VARIABLE_REQUESTED_WITH_WRONG_TYPE = - '%s: Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.'; -export const VALID_BUCKETING_ID = '%s: BucketingId is valid: "%s"'; -export const BUCKETING_ID_NOT_STRING = '%s: BucketingID attribute is not a string. Defaulted to userId'; -export const EVALUATING_AUDIENCE = '%s: Starting to evaluate audience "%s" with conditions: %s.'; -export const EVALUATING_AUDIENCES_COMBINED = '%s: Evaluating audiences for %s "%s": %s.'; -export const AUDIENCE_EVALUATION_RESULT = '%s: Audience "%s" evaluated to %s.'; -export const AUDIENCE_EVALUATION_RESULT_COMBINED = '%s: Audiences for %s %s collectively evaluated to %s.'; + 'Variable "%s" is not used in variation "%s". Returning default value.'; +export const USER_RECEIVED_VARIABLE_VALUE = 'Got variable value "%s" for variable "%s" of feature flag "%s"'; +export const VALID_DATAFILE = 'Datafile is valid.'; +export const VALID_USER_PROFILE_SERVICE = 'Valid user profile service provided.'; +export const VARIATION_REMOVED_FOR_USER = 'Variation mapped to experiment %s has been removed for user %s.'; + +export const VALID_BUCKETING_ID = 'BucketingId is valid: "%s"'; +export const EVALUATING_AUDIENCE = 'Starting to evaluate audience "%s" with conditions: %s.'; +export const EVALUATING_AUDIENCES_COMBINED = 'Evaluating audiences for %s "%s": %s.'; +export const AUDIENCE_EVALUATION_RESULT = 'Audience "%s" evaluated to %s.'; +export const AUDIENCE_EVALUATION_RESULT_COMBINED = 'Audiences for %s %s collectively evaluated to %s.'; export const MISSING_ATTRIBUTE_VALUE = - '%s: Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".'; -export const UNEXPECTED_CONDITION_VALUE = - '%s: Audience condition %s evaluated to UNKNOWN because the condition value is not supported.'; -export const UNEXPECTED_TYPE = - '%s: Audience condition %s evaluated to UNKNOWN because a value of type "%s" was passed for user attribute "%s".'; + 'Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".'; export const UNEXPECTED_TYPE_NULL = - '%s: Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".'; -export const UPDATED_OPTIMIZELY_CONFIG = '%s: Updated Optimizely config to revision %s (project id %s)'; -export const OUT_OF_BOUNDS = - '%s: Audience condition %s evaluated to UNKNOWN because the number value for user attribute "%s" is not in the range [-2^53, +2^53].'; -export const UNABLE_TO_ATTACH_UNLOAD = '%s: unable to bind optimizely.close() to page unload event: "%s"'; + 'Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".'; +export const UPDATED_OPTIMIZELY_CONFIG = 'Updated Optimizely config to revision %s (project id %s)'; +export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to page unload event: "%s"'; export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; export const ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN = 'Adding Authorization header with Bearer Token'; export const MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS = 'Making datafile request to url %s with headers: %s'; export const RESPONSE_STATUS_CODE = 'Response status code: %s'; export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; +export const messages: string[] = []; diff --git a/lib/logging/logger.spec.ts b/lib/logging/logger.spec.ts new file mode 100644 index 000000000..e0a8d6ac6 --- /dev/null +++ b/lib/logging/logger.spec.ts @@ -0,0 +1,389 @@ +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; + +it.skip('pass', () => {}); +// import { +// LogLevel, +// LogHandler, +// LoggerFacade, +// } from './models' + +// import { +// setLogHandler, +// setLogLevel, +// getLogger, +// ConsoleLogHandler, +// resetLogger, +// getLogLevel, +// } from './logger' + +// import { resetErrorHandler } from './errorHandler' +// import { ErrorHandler, setErrorHandler } from './errorHandler' + +// describe('logger', () => { +// afterEach(() => { +// resetLogger() +// resetErrorHandler() +// }) + +// describe('OptimizelyLogger', () => { +// let stubLogger: LogHandler +// let logger: LoggerFacade +// let stubErrorHandler: ErrorHandler + +// beforeEach(() => { +// stubLogger = { +// log: vi.fn(), +// } +// stubErrorHandler = { +// handleError: vi.fn(), +// } +// setLogLevel(LogLevel.DEBUG) +// setLogHandler(stubLogger) +// setErrorHandler(stubErrorHandler) +// logger = getLogger() +// }) + +// describe('setLogLevel', () => { +// it('should coerce "debug"', () => { +// setLogLevel('debug') +// expect(getLogLevel()).toBe(LogLevel.DEBUG) +// }) + +// it('should coerce "deBug"', () => { +// setLogLevel('deBug') +// expect(getLogLevel()).toBe(LogLevel.DEBUG) +// }) + +// it('should coerce "INFO"', () => { +// setLogLevel('INFO') +// expect(getLogLevel()).toBe(LogLevel.INFO) +// }) + +// it('should coerce "WARN"', () => { +// setLogLevel('WARN') +// expect(getLogLevel()).toBe(LogLevel.WARNING) +// }) + +// it('should coerce "warning"', () => { +// setLogLevel('warning') +// expect(getLogLevel()).toBe(LogLevel.WARNING) +// }) + +// it('should coerce "ERROR"', () => { +// setLogLevel('WARN') +// expect(getLogLevel()).toBe(LogLevel.WARNING) +// }) + +// it('should default to error if invalid', () => { +// setLogLevel('invalid') +// expect(getLogLevel()).toBe(LogLevel.ERROR) +// }) +// }) + +// describe('getLogger(name)', () => { +// it('should prepend the name in the log messages', () => { +// const myLogger = getLogger('doit') +// myLogger.info('test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'doit: test') +// }) +// }) + +// describe('logger.log(level, msg)', () => { +// it('should work with a string logLevel', () => { +// setLogLevel(LogLevel.INFO) +// logger.log('info', 'test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') +// }) + +// it('should call the loggerBackend when the message logLevel is equal to the configured logLevel threshold', () => { +// setLogLevel(LogLevel.INFO) +// logger.log(LogLevel.INFO, 'test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') +// }) + +// it('should call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { +// setLogLevel(LogLevel.INFO) +// logger.log(LogLevel.WARNING, 'test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') +// }) + +// it('should not call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { +// setLogLevel(LogLevel.INFO) +// logger.log(LogLevel.DEBUG, 'test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(0) +// }) + +// it('should not throw if loggerBackend is not supplied', () => { +// setLogLevel(LogLevel.INFO) +// logger.log(LogLevel.ERROR, 'test') +// }) +// }) + +// describe('logger.info', () => { +// it('should handle info(message)', () => { +// logger.info('test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') +// }) +// it('should handle info(message, ...splat)', () => { +// logger.info('test: %s %s', 'hey', 'jude') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey jude') +// }) + +// it('should handle info(message, ...splat, error)', () => { +// const error = new Error('hey') +// logger.info('test: %s', 'hey', error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) + +// it('should handle info(error)', () => { +// const error = new Error('hey') +// logger.info(error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) +// }) + +// describe('logger.debug', () => { +// it('should handle debug(message)', () => { +// logger.debug('test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test') +// }) + +// it('should handle debug(message, ...splat)', () => { +// logger.debug('test: %s', 'hey') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') +// }) + +// it('should handle debug(message, ...splat, error)', () => { +// const error = new Error('hey') +// logger.debug('test: %s', 'hey', error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) + +// it('should handle debug(error)', () => { +// const error = new Error('hey') +// logger.debug(error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) +// }) + +// describe('logger.warn', () => { +// it('should handle warn(message)', () => { +// logger.warn('test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') +// }) + +// it('should handle warn(message, ...splat)', () => { +// logger.warn('test: %s', 'hey') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') +// }) + +// it('should handle warn(message, ...splat, error)', () => { +// const error = new Error('hey') +// logger.warn('test: %s', 'hey', error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) + +// it('should handle info(error)', () => { +// const error = new Error('hey') +// logger.warn(error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) +// }) + +// describe('logger.error', () => { +// it('should handle error(message)', () => { +// logger.error('test') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test') +// }) + +// it('should handle error(message, ...splat)', () => { +// logger.error('test: %s', 'hey') + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') +// }) + +// it('should handle error(message, ...splat, error)', () => { +// const error = new Error('hey') +// logger.error('test: %s', 'hey', error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) + +// it('should handle error(error)', () => { +// const error = new Error('hey') +// logger.error(error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) + +// it('should work with an insufficient amount of splat args error(msg, ...splat, message)', () => { +// const error = new Error('hey') +// logger.error('hey %s', error) + +// expect(stubLogger.log).toHaveBeenCalledTimes(1) +// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey undefined') +// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) +// }) +// }) + +// describe('using ConsoleLoggerHandler', () => { +// beforeEach(() => { +// vi.spyOn(console, 'info').mockImplementation(() => {}) +// }) + +// afterEach(() => { +// vi.resetAllMocks() +// }) + +// it('should work with BasicLogger', () => { +// const logger = new ConsoleLogHandler() +// const TIME = '12:00' +// setLogHandler(logger) +// setLogLevel(LogLevel.INFO) +// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) + +// logger.log(LogLevel.INFO, 'hey') + +// expect(console.info).toBeCalledTimes(1) +// expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 hey') +// }) + +// it('should set logLevel to ERROR when setLogLevel is called with invalid value', () => { +// const logger = new ConsoleLogHandler() +// logger.setLogLevel('invalid' as any) + +// expect(logger.logLevel).toEqual(LogLevel.ERROR) +// }) + +// it('should set logLevel to ERROR when setLogLevel is called with no value', () => { +// const logger = new ConsoleLogHandler() +// // eslint-disable-next-line @typescript-eslint/ban-ts-comment +// // @ts-ignore +// logger.setLogLevel() + +// expect(logger.logLevel).toEqual(LogLevel.ERROR) +// }) +// }) +// }) + +// describe('ConsoleLogger', function() { +// beforeEach(() => { +// vi.spyOn(console, 'info') +// vi.spyOn(console, 'log') +// vi.spyOn(console, 'warn') +// vi.spyOn(console, 'error') +// }) + +// afterEach(() => { +// vi.resetAllMocks() +// }) + +// it('should log to console.info for LogLevel.INFO', () => { +// const logger = new ConsoleLogHandler({ +// logLevel: LogLevel.DEBUG, +// }) +// const TIME = '12:00' +// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) + +// logger.log(LogLevel.INFO, 'test') + +// expect(console.info).toBeCalledTimes(1) +// expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 test') +// }) + +// it('should log to console.log for LogLevel.DEBUG', () => { +// const logger = new ConsoleLogHandler({ +// logLevel: LogLevel.DEBUG, +// }) +// const TIME = '12:00' +// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) + +// logger.log(LogLevel.DEBUG, 'debug') + +// expect(console.log).toBeCalledTimes(1) +// expect(console.log).toBeCalledWith('[OPTIMIZELY] - DEBUG 12:00 debug') +// }) + +// it('should log to console.warn for LogLevel.WARNING', () => { +// const logger = new ConsoleLogHandler({ +// logLevel: LogLevel.DEBUG, +// }) +// const TIME = '12:00' +// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) + +// logger.log(LogLevel.WARNING, 'warning') + +// expect(console.warn).toBeCalledTimes(1) +// expect(console.warn).toBeCalledWith('[OPTIMIZELY] - WARN 12:00 warning') +// }) + +// it('should log to console.error for LogLevel.ERROR', () => { +// const logger = new ConsoleLogHandler({ +// logLevel: LogLevel.DEBUG, +// }) +// const TIME = '12:00' +// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) + +// logger.log(LogLevel.ERROR, 'error') + +// expect(console.error).toBeCalledTimes(1) +// expect(console.error).toBeCalledWith('[OPTIMIZELY] - ERROR 12:00 error') +// }) + +// it('should not log if the configured logLevel is higher', () => { +// const logger = new ConsoleLogHandler({ +// logLevel: LogLevel.INFO, +// }) + +// logger.log(LogLevel.DEBUG, 'debug') + +// expect(console.log).toBeCalledTimes(0) +// }) +// }) +// }) diff --git a/lib/logging/logger.ts b/lib/logging/logger.ts new file mode 100644 index 000000000..408a06710 --- /dev/null +++ b/lib/logging/logger.ts @@ -0,0 +1,143 @@ +/** + * Copyright 2019, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { OptimizelyError } from '../error/optimizly_error'; +import { MessageResolver } from '../message/message_resolver'; +import { sprintf } from '../utils/fns' + +export enum LogLevel { + Debug, + Info, + Warn, + Error, +} + +export interface LoggerFacade { + info(message: string | Error, ...args: any[]): void; + debug(message: string | Error, ...args: any[]): void; + warn(message: string | Error, ...args: any[]): void; + error(message: string | Error, ...args: any[]): void; + child(name: string): LoggerFacade; +} + +export interface LogHandler { + log(level: LogLevel, message: string, ...args: any[]): void +} + +export class ConsoleLogHandler implements LogHandler { + private prefix: string + + constructor(prefix?: string) { + this.prefix = prefix || '[OPTIMIZELY]' + } + + log(level: LogLevel, message: string) : void { + const log = `${this.prefix} - ${level} ${this.getTime()} ${message}` + this.consoleLog(level, log) + } + + private getTime(): string { + return new Date().toISOString() + } + + private consoleLog(logLevel: LogLevel, log: string) : void { + const methodName = LogLevel[logLevel].toLowerCase() + const method: any = console[methodName as keyof Console] || console.log; + method.bind(console)(log); + } +} + +type OptimizelyLoggerConfig = { + logHandler: LogHandler, + infoMsgResolver?: MessageResolver, + errorMsgResolver: MessageResolver, + level: LogLevel, + name?: string, +}; + +export class OptimizelyLogger implements LoggerFacade { + private name?: string; + private prefix: string; + private logHandler: LogHandler; + private infoResolver?: MessageResolver; + private errorResolver: MessageResolver; + private level: LogLevel; + + constructor(config: OptimizelyLoggerConfig) { + this.logHandler = config.logHandler; + this.infoResolver = config.infoMsgResolver; + this.errorResolver = config.errorMsgResolver; + this.level = config.level; + this.name = config.name; + this.prefix = this.name ? `${this.name}: ` : ''; + } + + child(name: string): OptimizelyLogger { + return new OptimizelyLogger({ + logHandler: this.logHandler, + infoMsgResolver: this.infoResolver, + errorMsgResolver: this.errorResolver, + level: this.level, + name: `${this.name}.${name}`, + }); + } + + info(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Info, message, args) + } + + debug(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Debug, message, args) + } + + warn(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Warn, message, args) + } + + error(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Error, message, args) + } + + private handleLog(level: LogLevel, message: string, args: any[]) { + const log = `${this.prefix}${sprintf(message, ...args)}` + this.logHandler.log(level, log); + } + + private log(level: LogLevel, message: string | Error, ...args: any[]): void { + if (level < this.level) { + return; + } + + if (message instanceof Error) { + if (message instanceof OptimizelyError) { + message.setMessage(this.errorResolver); + } + this.handleLog(level, message.message, []); + return; + } + + let resolver = this.errorResolver; + + if (level < LogLevel.Warn) { + if (!this.infoResolver) { + return; + } + resolver = this.infoResolver; + } + + const resolvedMessage = resolver.resolve(message); + this.handleLog(level, resolvedMessage, args); + } +} diff --git a/lib/logging/logger_factory.ts b/lib/logging/logger_factory.ts new file mode 100644 index 000000000..37e68a801 --- /dev/null +++ b/lib/logging/logger_factory.ts @@ -0,0 +1,20 @@ +// import { LogLevel, LogResolver } from './logger'; + +// type LevelPreset = { +// level: LogLevel; +// resolver?: LogResolver; +// } + +// const levelPresetSymbol = Symbol('levelPreset'); + +// export type OpaqueLevelPreset = { +// [levelPresetSymbol]: unknown; +// }; + +// const Info: LevelPreset = { +// level: LogLevel.Info, +// }; + +// export const InfoLog: OpaqueLevelPreset = { +// [levelPresetSymbol]: Info, +// }; diff --git a/lib/message/message_resolver.ts b/lib/message/message_resolver.ts new file mode 100644 index 000000000..4f6d38752 --- /dev/null +++ b/lib/message/message_resolver.ts @@ -0,0 +1,20 @@ +import { messages as infoMessages } from '../log_messages'; +import { messages as errorMessages } from '../error_messages'; + +export interface MessageResolver { + resolve(baseMessage: string): string; +} + +export const infoResolver: MessageResolver = { + resolve(baseMessage: string): string { + const messageNum = parseInt(baseMessage); + return infoMessages[messageNum] || baseMessage; + } +}; + +export const errorResolver: MessageResolver = { + resolve(baseMessage: string): string { + const messageNum = parseInt(baseMessage); + return errorMessages[messageNum] || baseMessage; + } +}; diff --git a/lib/modules/logging/errorHandler.ts b/lib/modules/logging/errorHandler.ts deleted file mode 100644 index bb659aeae..000000000 --- a/lib/modules/logging/errorHandler.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * @export - * @interface ErrorHandler - */ -export interface ErrorHandler { - /** - * @param {Error} exception - * @memberof ErrorHandler - */ - handleError(exception: Error): void -} - -/** - * @export - * @class NoopErrorHandler - * @implements {ErrorHandler} - */ -export class NoopErrorHandler implements ErrorHandler { - /** - * @param {Error} exception - * @memberof NoopErrorHandler - */ - handleError(exception: Error): void { - // no-op - return - } -} - -let globalErrorHandler: ErrorHandler = new NoopErrorHandler() - -/** - * @export - * @param {ErrorHandler} handler - */ -export function setErrorHandler(handler: ErrorHandler): void { - globalErrorHandler = handler -} - -/** - * @export - * @returns {ErrorHandler} - */ -export function getErrorHandler(): ErrorHandler { - return globalErrorHandler -} - -/** - * @export - */ -export function resetErrorHandler(): void { - globalErrorHandler = new NoopErrorHandler() -} diff --git a/lib/modules/logging/logger.spec.ts b/lib/modules/logging/logger.spec.ts deleted file mode 100644 index 0440755bb..000000000 --- a/lib/modules/logging/logger.spec.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; - -import { - LogLevel, - LogHandler, - LoggerFacade, -} from './models' - -import { - setLogHandler, - setLogLevel, - getLogger, - ConsoleLogHandler, - resetLogger, - getLogLevel, -} from './logger' - -import { resetErrorHandler } from './errorHandler' -import { ErrorHandler, setErrorHandler } from './errorHandler' - -describe('logger', () => { - afterEach(() => { - resetLogger() - resetErrorHandler() - }) - - describe('OptimizelyLogger', () => { - let stubLogger: LogHandler - let logger: LoggerFacade - let stubErrorHandler: ErrorHandler - - beforeEach(() => { - stubLogger = { - log: vi.fn(), - } - stubErrorHandler = { - handleError: vi.fn(), - } - setLogLevel(LogLevel.DEBUG) - setLogHandler(stubLogger) - setErrorHandler(stubErrorHandler) - logger = getLogger() - }) - - describe('setLogLevel', () => { - it('should coerce "debug"', () => { - setLogLevel('debug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "deBug"', () => { - setLogLevel('deBug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "INFO"', () => { - setLogLevel('INFO') - expect(getLogLevel()).toBe(LogLevel.INFO) - }) - - it('should coerce "WARN"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "warning"', () => { - setLogLevel('warning') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "ERROR"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should default to error if invalid', () => { - setLogLevel('invalid') - expect(getLogLevel()).toBe(LogLevel.ERROR) - }) - }) - - describe('getLogger(name)', () => { - it('should prepend the name in the log messages', () => { - const myLogger = getLogger('doit') - myLogger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'doit: test') - }) - }) - - describe('logger.log(level, msg)', () => { - it('should work with a string logLevel', () => { - setLogLevel(LogLevel.INFO) - logger.log('info', 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is equal to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.INFO, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.WARNING, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should not call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.DEBUG, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(0) - }) - - it('should not throw if loggerBackend is not supplied', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.ERROR, 'test') - }) - }) - - describe('logger.info', () => { - it('should handle info(message)', () => { - logger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - it('should handle info(message, ...splat)', () => { - logger.info('test: %s %s', 'hey', 'jude') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey jude') - }) - - it('should handle info(message, ...splat, error)', () => { - const error = new Error('hey') - logger.info('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.info(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.debug', () => { - it('should handle debug(message)', () => { - logger.debug('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test') - }) - - it('should handle debug(message, ...splat)', () => { - logger.debug('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - }) - - it('should handle debug(message, ...splat, error)', () => { - const error = new Error('hey') - logger.debug('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle debug(error)', () => { - const error = new Error('hey') - logger.debug(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.warn', () => { - it('should handle warn(message)', () => { - logger.warn('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should handle warn(message, ...splat)', () => { - logger.warn('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - }) - - it('should handle warn(message, ...splat, error)', () => { - const error = new Error('hey') - logger.warn('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.warn(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.error', () => { - it('should handle error(message)', () => { - logger.error('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test') - }) - - it('should handle error(message, ...splat)', () => { - logger.error('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - }) - - it('should handle error(message, ...splat, error)', () => { - const error = new Error('hey') - logger.error('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle error(error)', () => { - const error = new Error('hey') - logger.error(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should work with an insufficient amount of splat args error(msg, ...splat, message)', () => { - const error = new Error('hey') - logger.error('hey %s', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey undefined') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('using ConsoleLoggerHandler', () => { - beforeEach(() => { - vi.spyOn(console, 'info').mockImplementation(() => {}) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('should work with BasicLogger', () => { - const logger = new ConsoleLogHandler() - const TIME = '12:00' - setLogHandler(logger) - setLogLevel(LogLevel.INFO) - vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'hey') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 hey') - }) - - it('should set logLevel to ERROR when setLogLevel is called with invalid value', () => { - const logger = new ConsoleLogHandler() - logger.setLogLevel('invalid' as any) - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - - it('should set logLevel to ERROR when setLogLevel is called with no value', () => { - const logger = new ConsoleLogHandler() - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - logger.setLogLevel() - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - }) - }) - - describe('ConsoleLogger', function() { - beforeEach(() => { - vi.spyOn(console, 'info') - vi.spyOn(console, 'log') - vi.spyOn(console, 'warn') - vi.spyOn(console, 'error') - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('should log to console.info for LogLevel.INFO', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'test') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 test') - }) - - it('should log to console.log for LogLevel.DEBUG', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(1) - expect(console.log).toBeCalledWith('[OPTIMIZELY] - DEBUG 12:00 debug') - }) - - it('should log to console.warn for LogLevel.WARNING', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.WARNING, 'warning') - - expect(console.warn).toBeCalledTimes(1) - expect(console.warn).toBeCalledWith('[OPTIMIZELY] - WARN 12:00 warning') - }) - - it('should log to console.error for LogLevel.ERROR', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.ERROR, 'error') - - expect(console.error).toBeCalledTimes(1) - expect(console.error).toBeCalledWith('[OPTIMIZELY] - ERROR 12:00 error') - }) - - it('should not log if the configured logLevel is higher', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.INFO, - }) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(0) - }) - }) -}) diff --git a/lib/modules/logging/logger.ts b/lib/modules/logging/logger.ts deleted file mode 100644 index e58664fb1..000000000 --- a/lib/modules/logging/logger.ts +++ /dev/null @@ -1,333 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getErrorHandler } from './errorHandler' -import { isValidEnum, sprintf } from '../../utils/fns' - -import { LogLevel, LoggerFacade, LogManager, LogHandler } from './models' - -type StringToLogLevel = { - NOTSET: number, - DEBUG: number, - INFO: number, - WARNING: number, - ERROR: number, -} - -const stringToLogLevel: StringToLogLevel = { - NOTSET: 0, - DEBUG: 1, - INFO: 2, - WARNING: 3, - ERROR: 4, -} - -function coerceLogLevel(level: any): LogLevel { - if (typeof level !== 'string') { - return level - } - - level = level.toUpperCase() - if (level === 'WARN') { - level = 'WARNING' - } - - if (!stringToLogLevel[level as keyof StringToLogLevel]) { - return level - } - - return stringToLogLevel[level as keyof StringToLogLevel] -} - -type LogData = { - message: string - splat: any[] - error?: Error -} - -class DefaultLogManager implements LogManager { - private loggers: { - [name: string]: LoggerFacade - } - private defaultLoggerFacade = new OptimizelyLogger() - - constructor() { - this.loggers = {} - } - - getLogger(name?: string): LoggerFacade { - if (!name) { - return this.defaultLoggerFacade - } - - if (!this.loggers[name]) { - this.loggers[name] = new OptimizelyLogger({ messagePrefix: name }) - } - - return this.loggers[name] - } -} - -type ConsoleLogHandlerConfig = { - logLevel?: LogLevel | string - logToConsole?: boolean - prefix?: string -} - -export class ConsoleLogHandler implements LogHandler { - public logLevel: LogLevel - private logToConsole: boolean - private prefix: string - - /** - * Creates an instance of ConsoleLogger. - * @param {ConsoleLogHandlerConfig} config - * @memberof ConsoleLogger - */ - constructor(config: ConsoleLogHandlerConfig = {}) { - this.logLevel = LogLevel.NOTSET - if (config.logLevel !== undefined && isValidEnum(LogLevel, config.logLevel)) { - this.setLogLevel(config.logLevel) - } - - this.logToConsole = config.logToConsole !== undefined ? !!config.logToConsole : true - this.prefix = config.prefix !== undefined ? config.prefix : '[OPTIMIZELY]' - } - - /** - * @param {LogLevel} level - * @param {string} message - * @memberof ConsoleLogger - */ - log(level: LogLevel, message: string) : void { - if (!this.shouldLog(level) || !this.logToConsole) { - return - } - - const logMessage = `${this.prefix} - ${this.getLogLevelName( - level, - )} ${this.getTime()} ${message}` - - this.consoleLog(level, [logMessage]) - } - - /** - * @param {LogLevel} level - * @memberof ConsoleLogger - */ - setLogLevel(level: LogLevel | string) : void { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - this.logLevel = LogLevel.ERROR - } else { - this.logLevel = level - } - } - - /** - * @returns {string} - * @memberof ConsoleLogger - */ - getTime(): string { - return new Date().toISOString() - } - - /** - * @private - * @param {LogLevel} targetLogLevel - * @returns {boolean} - * @memberof ConsoleLogger - */ - private shouldLog(targetLogLevel: LogLevel): boolean { - return targetLogLevel >= this.logLevel - } - - /** - * @private - * @param {LogLevel} logLevel - * @returns {string} - * @memberof ConsoleLogger - */ - private getLogLevelName(logLevel: LogLevel): string { - switch (logLevel) { - case LogLevel.DEBUG: - return 'DEBUG' - case LogLevel.INFO: - return 'INFO ' - case LogLevel.WARNING: - return 'WARN ' - case LogLevel.ERROR: - return 'ERROR' - default: - return 'NOTSET' - } - } - - /** - * @private - * @param {LogLevel} logLevel - * @param {string[]} logArguments - * @memberof ConsoleLogger - */ - private consoleLog(logLevel: LogLevel, logArguments: [string, ...string[]]) { - switch (logLevel) { - case LogLevel.DEBUG: - console.log(...logArguments) - break - case LogLevel.INFO: - console.info(...logArguments) - break - case LogLevel.WARNING: - console.warn(...logArguments) - break - case LogLevel.ERROR: - console.error(...logArguments) - break - default: - console.log(...logArguments) - } - } -} - -let globalLogLevel: LogLevel = LogLevel.NOTSET -let globalLogHandler: LogHandler | null = null - -class OptimizelyLogger implements LoggerFacade { - private messagePrefix = '' - - constructor(opts: { messagePrefix?: string } = {}) { - if (opts.messagePrefix) { - this.messagePrefix = opts.messagePrefix - } - } - - /** - * @param {(LogLevel | LogInputObject)} levelOrObj - * @param {string} [message] - * @memberof OptimizelyLogger - */ - log(level: LogLevel | string, message: string, ...splat: any[]): void { - this.internalLog(coerceLogLevel(level), { - message, - splat, - }) - } - - info(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.INFO, message, splat) - } - - debug(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.DEBUG, message, splat) - } - - warn(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.WARNING, message, splat) - } - - error(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.ERROR, message, splat) - } - - private format(data: LogData): string { - return `${this.messagePrefix ? this.messagePrefix + ': ' : ''}${sprintf( - data.message, - ...data.splat, - )}` - } - - private internalLog(level: LogLevel, data: LogData): void { - if (!globalLogHandler) { - return - } - - if (level < globalLogLevel) { - return - } - - globalLogHandler.log(level, this.format(data)) - - if (data.error && data.error instanceof Error) { - getErrorHandler().handleError(data.error) - } - } - - private namedLog(level: LogLevel, message: string | Error, splat: any[]): void { - let error: Error | undefined - - if (message instanceof Error) { - error = message - message = error.message - this.internalLog(level, { - error, - message, - splat, - }) - return - } - - if (splat.length === 0) { - this.internalLog(level, { - message, - splat, - }) - return - } - - const last = splat[splat.length - 1] - if (last instanceof Error) { - error = last - splat.splice(-1) - } - - this.internalLog(level, { message, error, splat }) - } -} - -let globalLogManager: LogManager = new DefaultLogManager() - -export function getLogger(name?: string): LoggerFacade { - return globalLogManager.getLogger(name) -} - -export function setLogHandler(logger: LogHandler | null) : void { - globalLogHandler = logger -} - -export function setLogLevel(level: LogLevel | string) : void { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - globalLogLevel = LogLevel.ERROR - } else { - globalLogLevel = level - } -} - -export function getLogLevel(): LogLevel { - return globalLogLevel -} - -/** - * Resets all global logger state to it's original - */ -export function resetLogger() : void { - globalLogManager = new DefaultLogManager() - globalLogLevel = LogLevel.NOTSET -} - -export default { - setLogLevel: setLogLevel, - setLogHandler: setLogHandler -} diff --git a/lib/modules/logging/models.ts b/lib/modules/logging/models.ts deleted file mode 100644 index cd3223932..000000000 --- a/lib/modules/logging/models.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export enum LogLevel { - NOTSET = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - ERROR = 4, -} - -export interface LoggerFacade { - log(level: LogLevel | string, message: string, ...splat: any[]): void - - info(message: string | Error, ...splat: any[]): void - - debug(message: string | Error, ...splat: any[]): void - - warn(message: string | Error, ...splat: any[]): void - - error(message: string | Error, ...splat: any[]): void -} - -export interface LogManager { - getLogger(name?: string): LoggerFacade -} - -export interface LogHandler { - log(level: LogLevel, message: string, ...splat: any[]): void -} diff --git a/lib/notification_center/index.tests.js b/lib/notification_center/index.tests.js index 2a398c4cf..a7bf83cee 100644 --- a/lib/notification_center/index.tests.js +++ b/lib/notification_center/index.tests.js @@ -18,12 +18,20 @@ import { assert } from 'chai'; import { createNotificationCenter } from './'; import * as enums from '../utils/enums'; -import { createLogger } from '../plugins/logger'; import errorHandler from '../plugins/error_handler'; import { NOTIFICATION_TYPES } from './type'; +import { create } from 'lodash'; var LOG_LEVEL = enums.LOG_LEVEL; +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/notification_center', function() { describe('APIs', function() { var mockLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); diff --git a/lib/notification_center/index.ts b/lib/notification_center/index.ts index 4df708096..15886fde3 100644 --- a/lib/notification_center/index.ts +++ b/lib/notification_center/index.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LogHandler, ErrorHandler } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger'; +import { ErrorHandler } from '../error/error_handler'; import { objectValues } from '../utils/fns'; import { @@ -24,14 +25,17 @@ import { NOTIFICATION_TYPES } from './type'; import { NotificationType, NotificationPayload } from './type'; import { Consumer, Fn } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; -import { NOTIFICATION_LISTENER_EXCEPTION } from '../log_messages'; +import { NOTIFICATION_LISTENER_EXCEPTION } from '../error_messages'; +import { ErrorReporter } from '../error/error_reporter'; +import { ErrorNotifier } from '../error/error_notifier'; const MODULE_NAME = 'NOTIFICATION_CENTER'; interface NotificationCenterOptions { - logger: LogHandler; - errorHandler: ErrorHandler; + logger?: LoggerFacade; + errorNotifier?: ErrorNotifier; } + export interface NotificationCenter { addNotificationListener<N extends NotificationType>( notificationType: N, @@ -56,8 +60,7 @@ export interface NotificationSender { * - TRACK a conversion event will be sent to Optimizely */ export class DefaultNotificationCenter implements NotificationCenter, NotificationSender { - private logger: LogHandler; - private errorHandler: ErrorHandler; + private errorReporter: ErrorReporter; private removerId = 1; private eventEmitter: EventEmitter<NotificationPayload> = new EventEmitter(); @@ -70,8 +73,7 @@ export class DefaultNotificationCenter implements NotificationCenter, Notificati * @param {ErrorHandler} options.errorHandler An instance of errorHandler to handle any unexpected error */ constructor(options: NotificationCenterOptions) { - this.logger = options.logger; - this.errorHandler = options.errorHandler; + this.errorReporter = new ErrorReporter(options.logger, options.errorNotifier); } /** @@ -96,12 +98,12 @@ export class DefaultNotificationCenter implements NotificationCenter, Notificati const returnId = this.removerId++; const remover = this.eventEmitter.on( - notificationType, this.wrapWithErrorHandling(notificationType, callback)); + notificationType, this.wrapWithErrorReporting(notificationType, callback)); this.removers.set(returnId, remover); return returnId; } - private wrapWithErrorHandling<N extends NotificationType>( + private wrapWithErrorReporting<N extends NotificationType>( notificationType: N, callback: Consumer<NotificationPayload[N]> ): Consumer<NotificationPayload[N]> { @@ -109,13 +111,8 @@ export class DefaultNotificationCenter implements NotificationCenter, Notificati try { callback(notificationData); } catch (ex: any) { - this.logger.log( - LOG_LEVEL.ERROR, - NOTIFICATION_LISTENER_EXCEPTION, - MODULE_NAME, - notificationType, - ex.message, - ); + const message = ex instanceof Error ? ex.message : String(ex); + this.errorReporter.report(NOTIFICATION_LISTENER_EXCEPTION, notificationType, message); } }; } diff --git a/lib/odp/event_manager/odp_event_api_manager.ts b/lib/odp/event_manager/odp_event_api_manager.ts index 8ea4f7060..23dec6274 100644 --- a/lib/odp/event_manager/odp_event_api_manager.ts +++ b/lib/odp/event_manager/odp_event_api_manager.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LoggerFacade } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { OdpEvent } from './odp_event'; import { HttpMethod, RequestHandler } from '../../utils/http_request_handler/http'; import { OdpConfig } from '../odp_config'; diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 4029a3621..3a7b4a62a 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -15,7 +15,7 @@ */ import { v4 as uuidV4} from 'uuid'; -import { LoggerFacade } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger'; import { OdpIntegrationConfig, odpIntegrationsAreEqual } from './odp_config'; import { OdpEventManager } from './event_manager/odp_event_manager'; diff --git a/lib/odp/segment_manager/odp_segment_api_manager.ts b/lib/odp/segment_manager/odp_segment_api_manager.ts index 6b609a8a3..1c336b298 100644 --- a/lib/odp/segment_manager/odp_segment_api_manager.ts +++ b/lib/odp/segment_manager/odp_segment_api_manager.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LoggerFacade, LogLevel } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { validate } from '../../utils/json_schema_validator'; import { OdpResponseSchema } from './odp_response_schema'; import { ODP_USER_KEY } from '../constant'; diff --git a/lib/odp/segment_manager/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts index 1dc2eca42..71c300030 100644 --- a/lib/odp/segment_manager/odp_segment_manager.ts +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -19,7 +19,7 @@ import { OdpSegmentApiManager } from './odp_segment_api_manager'; import { OdpIntegrationConfig } from '../odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { ODP_USER_KEY } from '../constant'; -import { LoggerFacade } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { ODP_CONFIG_NOT_AVAILABLE, ODP_NOT_INTEGRATED } from '../../error_messages'; export interface OdpSegmentManager { diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 1825bb9a2..cb5210915 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -20,13 +20,12 @@ import * as jsonSchemaValidator from '../utils/json_schema_validator'; import { createNotificationCenter } from '../notification_center'; import testData from '../tests/test_data'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; +import { LoggerFacade } from '../logging/logger'; import { createProjectConfig } from '../project_config/project_config'; import { getMockLogger } from '../tests/mock/mock_logger'; import { createOdpManager } from '../odp/odp_manager_factory.node'; describe('Optimizely', () => { - const errorHandler = { handleError: function() {} }; - const eventDispatcher = { dispatchEvent: () => Promise.resolve({ statusCode: 200 }), }; @@ -34,7 +33,6 @@ describe('Optimizely', () => { const eventProcessor = getForwardingEventProcessor(eventDispatcher); const odpManager = createOdpManager({}); const logger = getMockLogger(); - const notificationCenter = createNotificationCenter({ logger, errorHandler }); it('should pass disposable options to the respective services', () => { const projectConfigManager = getMockProjectConfigManager({ @@ -48,14 +46,11 @@ describe('Optimizely', () => { new Optimizely({ clientEngine: 'node-sdk', projectConfigManager, - errorHandler, jsonSchemaValidator, logger, - notificationCenter, eventProcessor, odpManager, disposable: true, - isValidInstance: true, }); expect(projectConfigManager.makeDisposable).toHaveBeenCalled(); diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 4f121df29..d1468bced 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -17,7 +17,6 @@ import { assert, expect } from 'chai'; import sinon from 'sinon'; import { sprintf } from '../utils/fns'; import { NOTIFICATION_TYPES } from '../notification_center/type'; -import * as logging from '../modules/logging'; import Optimizely from './'; import OptimizelyUserContext from '../optimizely_user_context'; import { OptimizelyDecideOption } from '../shared_types'; @@ -27,7 +26,6 @@ import * as projectConfigManager from '../project_config/project_config_manager' import * as enums from '../utils/enums'; import errorHandler from '../plugins/error_handler'; import fns from '../utils/fns'; -import * as logger from '../plugins/logger'; import * as decisionService from '../core/decision_service'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; import * as projectConfig from '../project_config/project_config'; @@ -39,15 +37,13 @@ import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_m import { DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; import { AUDIENCE_EVALUATION_RESULT_COMBINED, - EVENT_KEY_NOT_FOUND, EXPERIMENT_NOT_RUNNING, FEATURE_HAS_NO_EXPERIMENTS, - FORCED_BUCKETING_FAILED, + FEATURE_NOT_ENABLED_FOR_USER, INVALID_CLIENT_ENGINE, INVALID_DEFAULT_DECIDE_OPTIONS, INVALID_OBJECT, NOT_ACTIVATING_USER, - NOT_TRACKING_USER, RETURNING_STORED_VARIATION, USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, USER_FORCED_IN_VARIATION, @@ -61,18 +57,24 @@ import { USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, USER_NOT_BUCKETED_INTO_TARGETING_RULE, USER_NOT_IN_EXPERIMENT, + USER_RECEIVED_DEFAULT_VARIABLE_VALUE, + VALID_USER_PROFILE_SERVICE, VARIATION_REMOVED_FOR_USER, } from '../log_messages'; import { EXPERIMENT_KEY_NOT_IN_DATAFILE, INVALID_ATTRIBUTES, + NOT_TRACKING_USER, + EVENT_KEY_NOT_FOUND, INVALID_EXPERIMENT_KEY, INVALID_INPUT_FORMAT, NO_VARIATION_FOR_EXPERIMENT_KEY, USER_NOT_IN_FORCED_VARIATION, + FORCED_BUCKETING_FAILED, } from '../error_messages'; import { FAILED_TO_STOP, ONREADY_TIMEOUT_EXPIRED, PROMISE_SHOULD_NOT_HAVE_RESOLVED } from '../exception_messages'; import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP } from '../core/bucketer'; +import { error } from 'console'; var LOG_LEVEL = enums.LOG_LEVEL; var DECISION_SOURCES = enums.DECISION_SOURCES; @@ -92,6 +94,20 @@ const getMockEventProcessor = (notificationCenter) => { return getForwardingEventProcessor(getMockEventDispatcher(), notificationCenter); } +const getMockErrorNotifier = () => { + return { + notify: sinon.spy(), + } +}; + +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(datafileObj), @@ -99,13 +115,15 @@ const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { const eventDispatcher = getMockEventDispatcher(); const eventProcessor = getForwardingEventProcessor(eventDispatcher); - const notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); + const errorNotifier = getMockErrorNotifier(); + + const notificationCenter = createNotificationCenter({ logger: createdLogger, errorNotifier }); + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); const optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + errorNotifier, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -117,43 +135,17 @@ const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { sinon.stub(notificationCenter, 'sendNotifications'); - return { optlyInstance, eventProcessor, eventDispatcher, notificationCenter, createdLogger } + return { optlyInstance, eventProcessor, eventDispatcher, notificationCenter, errorNotifier, createdLogger } } describe('lib/optimizely', function() { - var ProjectConfigManagerStub; - var globalStubErrorHandler; - var stubLogHandler; var clock; beforeEach(function() { - logging.setLogLevel('notset'); - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogHandler(stubLogHandler); - globalStubErrorHandler = { - handleError: sinon.stub(), - }; - logging.setErrorHandler(globalStubErrorHandler); - ProjectConfigManagerStub = sinon - .stub(projectConfigManager, 'createProjectConfigManager') - .callsFake(function(config) { - var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; - return { - stop: sinon.stub(), - getConfig: sinon.stub().returns(currentConfig), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns({ then: function() {} }), - }; - }); // sinon.stub(eventDispatcher, 'dispatchEvent'); clock = sinon.useFakeTimers(new Date()); }); afterEach(function() { - ProjectConfigManagerStub.restore(); - logging.resetErrorHandler(); - logging.resetLogger(); // eventDispatcher.dispatchEvent.restore(); clock.restore(); }); @@ -165,17 +157,23 @@ describe('lib/optimizely', function() { return Promise.resolve(null); }, }; - var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: stubErrorHandler }); var eventProcessor = getForwardingEventProcessor(stubEventDispatcher); beforeEach(function() { sinon.stub(stubErrorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); }); afterEach(function() { stubErrorHandler.handleError.restore(); - createdLogger.log.restore(); + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); }); describe('constructor', function() { @@ -189,9 +187,9 @@ describe('lib/optimizely', function() { eventProcessor, }); - sinon.assert.called(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_CLIENT_ENGINE, 'OPTIMIZELY', 'undefined')); + sinon.assert.called(createdLogger.info); + + assert.deepEqual(createdLogger.info.args[0], [INVALID_CLIENT_ENGINE, undefined]); }); it('should log if the defaultDecideOptions passed in are invalid', function() { @@ -206,9 +204,8 @@ describe('lib/optimizely', function() { eventProcessor, }); - sinon.assert.called(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_DEFAULT_DECIDE_OPTIONS, 'OPTIMIZELY')); + sinon.assert.called(createdLogger.debug); + assert.deepEqual(createdLogger.debug.args[0], [INVALID_DEFAULT_DECIDE_OPTIONS]); }); it('should allow passing `react-sdk` as the clientEngine', function() { @@ -256,8 +253,10 @@ describe('lib/optimizely', function() { UNSTABLE_conditionEvaluators: undefined, }); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, 'OPTIMIZELY: Valid user profile service provided.'); + sinon.assert.calledWith( + createdLogger.info, + VALID_USER_PROFILE_SERVICE, + ); }); it('should pass in a null user profile to the decision service if the provided user profile is invalid', function() { @@ -281,11 +280,12 @@ describe('lib/optimizely', function() { UNSTABLE_conditionEvaluators: undefined, }); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - "USER_PROFILE_SERVICE_VALIDATOR: Provided user profile service instance is in an invalid format: Missing function 'lookup'." - ); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // logMessage, + // "USER_PROFILE_SERVICE_VALIDATOR: Provided user profile service instance is in an invalid format: Missing function 'lookup'." + // ); + sinon.assert.called(createdLogger.warn); }); }); }); @@ -298,7 +298,7 @@ describe('lib/optimizely', function() { var eventDispatcher = getMockEventDispatcher(); var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); var eventProcessor = getForwardingEventProcessor(eventDispatcher, notificationCenter); - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -321,7 +321,10 @@ describe('lib/optimizely', function() { bucketStub = sinon.stub(bucketer, 'bucket'); sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); }); @@ -329,7 +332,10 @@ describe('lib/optimizely', function() { eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); errorHandler.handleError.restore(); - createdLogger.log.restore(); + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); }); @@ -787,21 +793,11 @@ describe('lib/optimizely', function() { bucketStub.returns(fakeDecisionResponse); assert.isNull(optlyInstance.activate('testExperiment', 'testUser')); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.called(createdLogger.log); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - USER_HAS_NO_FORCED_VARIATION, - 'DECISION_SERVICE', - 'testUser' - ); + sinon.assert.called(createdLogger.info); sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, + createdLogger.info, NOT_ACTIVATING_USER, - 'OPTIMIZELY', 'testUser', 'testExperiment' ); @@ -811,27 +807,8 @@ describe('lib/optimizely', function() { assert.isNull(optlyInstance.activate('testExperimentWithAudiences', 'testUser', { browser_type: 'chrome' })); sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - USER_HAS_NO_FORCED_VARIATION, - 'DECISION_SERVICE', - 'testUser' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - USER_NOT_IN_EXPERIMENT, - 'DECISION_SERVICE', - 'testUser', - 'testExperimentWithAudiences' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, + createdLogger.info, NOT_ACTIVATING_USER, - 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences' ); @@ -841,27 +818,8 @@ describe('lib/optimizely', function() { assert.isNull(optlyInstance.activate('groupExperiment1', 'testUser', { browser_type: 'chrome' })); sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - USER_HAS_NO_FORCED_VARIATION, - 'DECISION_SERVICE', - 'testUser' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - USER_NOT_IN_EXPERIMENT, - 'DECISION_SERVICE', - 'testUser', - 'groupExperiment1' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, + createdLogger.info, NOT_ACTIVATING_USER, - 'OPTIMIZELY', 'testUser', 'groupExperiment1' ); @@ -869,38 +827,39 @@ describe('lib/optimizely', function() { it('should return null if experiment is not running', function() { assert.isNull(optlyInstance.activate('testExperimentNotRunning', 'testUser')); - sinon.assert.calledTwice(createdLogger.log); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage1, - sprintf(EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') - ); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentNotRunning') + sinon.assert.calledWithExactly( + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'testExperimentNotRunning' ); }); it('should throw an error for invalid user ID', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + assert.isNull(optlyInstance.activate('testExperiment', null)); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + sinon.assert.calledOnce(errorNotifier.notify); - sinon.assert.calledTwice(createdLogger.log); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage1, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'null', 'testExperiment') - ); + // sinon.assert.calledTwice(createdLogger.log); + + // var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage1, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + + // var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); + // assert.strictEqual( + // logMessage2, + // sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'null', 'testExperiment') + // ); }); it('should log an error for invalid experiment key', function() { @@ -908,34 +867,46 @@ describe('lib/optimizely', function() { sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledTwice(createdLogger.log); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage1, - sprintf(INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') + sinon.assert.calledWithExactly( + createdLogger.debug, + INVALID_EXPERIMENT_KEY, + 'invalidExperimentKey' ); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'invalidExperimentKey') + + sinon.assert.calledWithExactly( + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'invalidExperimentKey' ); }); it('should throw an error for invalid attributes', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + sinon.stub(createdLogger, 'info'); + assert.isNull(optlyInstance.activate('testExperimentWithAudiences', 'testUser', [])); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - - sinon.assert.calledTwice(createdLogger.log); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage1, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences') + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + + // sinon.assert.calledTwice(createdLogger.log); + // var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage1, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); + // assert.strictEqual( + // logMessage2, + // sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences') + // ); + sinon.assert.calledWithExactly( + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'testExperimentWithAudiences' ); }); @@ -955,7 +926,7 @@ describe('lib/optimizely', function() { errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, - logger: logger.createLogger({ + logger: createLogger({ logLevel: enums.LOG_LEVEL.DEBUG, logToConsole: false, }), @@ -985,17 +956,6 @@ describe('lib/optimizely', function() { sinon.assert.calledTwice(Optimizely.prototype.validateInputs); - var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage0, - sprintf(USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage1, - sprintf(USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') - ); - var expectedObj = { url: 'https://logx.optimizely.com/v1/events', httpVerb: 'POST', @@ -1037,26 +997,6 @@ describe('lib/optimizely', function() { }); }); - it('should not activate when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, - }); - - createdLogger.log.reset(); - - instance.activate('testExperiment', 'testUser'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'activate')); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); - }); }); describe('#track', function() { @@ -1497,7 +1437,7 @@ describe('lib/optimizely', function() { optlyInstance.track('testEvent', 'testUser', undefined, '4200'); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(createdLogger.log); + sinon.assert.calledOnce(createdLogger.error); }); it('should track a user for an experiment not running', function() { @@ -1667,39 +1607,44 @@ describe('lib/optimizely', function() { }); it('should throw an error for invalid user ID', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + sinon.stub(createdLogger, 'info'); + optlyInstance.track('testEvent', null); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + sinon.assert.calledOnce(errorNotifier.notify); + + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should log a warning for an event key that is not in the datafile and a warning for not tracking user', function() { - optlyInstance.track('invalidEventKey', 'testUser'); + const { optlyInstance, errorNotifier, createdLogger, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); - sinon.assert.calledTwice(createdLogger.log); + sinon.stub(createdLogger, 'warn'); + + optlyInstance.track('invalidEventKey', 'testUser'); - var logCall1 = createdLogger.log.getCall(0); sinon.assert.calledWithExactly( - logCall1, - LOG_LEVEL.WARNING, + createdLogger.warn, EVENT_KEY_NOT_FOUND, - 'OPTIMIZELY', 'invalidEventKey' ); - var logCall2 = createdLogger.log.getCall(1); sinon.assert.calledWithExactly( - logCall2, - LOG_LEVEL.WARNING, + createdLogger.warn, NOT_TRACKING_USER, - 'OPTIMIZELY', 'testUser' ); @@ -1711,13 +1656,13 @@ describe('lib/optimizely', function() { sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(errorHandler.handleError); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); it('should not throw an error for an event key without associated experiment IDs', function() { @@ -1735,7 +1680,7 @@ describe('lib/optimizely', function() { errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, - logger: logger.createLogger({ + logger: createLogger({ logLevel: enums.LOG_LEVEL.DEBUG, logToConsole: false, }), @@ -1748,27 +1693,6 @@ describe('lib/optimizely', function() { instance.track('testEvent', 'testUser'); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); }); - - it('should not track when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, - }); - - createdLogger.log.reset(); - - instance.track('testExperiment', 'testUser'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'track')); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); - }); }); describe('#getVariation', function() { @@ -1783,15 +1707,6 @@ describe('lib/optimizely', function() { assert.strictEqual(variation, 'variation'); sinon.assert.calledOnce(bucketer.bucket); - sinon.assert.called(createdLogger.log); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - USER_HAS_NO_FORCED_VARIATION, - 'DECISION_SERVICE', - 'testUser' - ); }); it('should call bucketer and return variation key with attributes', function() { @@ -1807,7 +1722,6 @@ describe('lib/optimizely', function() { assert.strictEqual(getVariation, 'variationWithAudience'); sinon.assert.calledOnce(bucketer.bucket); - sinon.assert.called(createdLogger.log); }); it('should return null if user is not in audience or experiment is not running', function() { @@ -1818,72 +1732,54 @@ describe('lib/optimizely', function() { assert.isNull(getVariationReturnsNull2); sinon.assert.notCalled(bucketer.bucket); - sinon.assert.called(createdLogger.log); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - USER_HAS_NO_FORCED_VARIATION, - 'DECISION_SERVICE', - 'testUser' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - USER_NOT_IN_EXPERIMENT, - 'DECISION_SERVICE', - 'testUser', - 'testExperimentWithAudiences' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - EXPERIMENT_NOT_RUNNING, - 'DECISION_SERVICE', - 'testExperimentNotRunning' - ); }); it('should throw an error for invalid user ID', function() { + const { optlyInstance, errorNotifier, createdLogger, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + var getVariationWithError = optlyInstance.getVariation('testExperiment', null); assert.isNull(getVariationWithError); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should log an error for invalid experiment key', function() { var getVariationWithError = optlyInstance.getVariation('invalidExperimentKey', 'testUser'); assert.isNull(getVariationWithError); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf(INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') + sinon.assert.calledWithExactly( + createdLogger.debug, + INVALID_EXPERIMENT_KEY, + 'invalidExperimentKey' ); }); it('should throw an error for invalid attributes', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + var getVariationWithError = optlyInstance.getVariation('testExperimentWithAudiences', 'testUser', []); assert.isNull(getVariationWithError); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + sinon.assert.calledOnce(errorNotifier.notify); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); describe('whitelisting', function() { @@ -1900,41 +1796,7 @@ describe('lib/optimizely', function() { assert.strictEqual(getVariation, 'control'); sinon.assert.calledOnce(Optimizely.prototype.validateInputs); - - sinon.assert.calledTwice(createdLogger.log); - - var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage0, - sprintf(USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage, - sprintf(USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') - ); - }); - }); - - it('should not return variation when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, }); - - createdLogger.log.reset(); - - instance.getVariation('testExperiment', 'testUser'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getVariation')); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); describe('order of bucketing operations', function() { @@ -1982,41 +1844,38 @@ describe('lib/optimizely', function() { it('should return null when set has not been called', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperiment', 'user1'); assert.strictEqual(forcedVariation, null); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1')); }); it('should return null with a null experimentKey', function() { var forcedVariation = optlyInstance.getForcedVariation(null, 'user1'); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); }); it('should return null with an undefined experimentKey', function() { var forcedVariation = optlyInstance.getForcedVariation(undefined, 'user1'); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); }); it('should return null with a null userId', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperiment', null); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should return null with an undefined userId', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperiment', undefined); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); }); @@ -2024,12 +1883,6 @@ describe('lib/optimizely', function() { it('should be able to set a forced variation', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'user1', 'control'); assert.strictEqual(didSetVariation, true); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') - ); }); it('should override bucketing in optlyInstance.getVariation', function() { @@ -2060,25 +1913,6 @@ describe('lib/optimizely', function() { var forcedVariation2 = optlyInstance.getForcedVariation('testExperiment', 'user1'); assert.strictEqual(forcedVariation2, null); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - var variationIsMappedLogMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); - var variationMappingRemovedLogMessage = buildLogMessageFromArgs(createdLogger.log.args[2]); - - assert.strictEqual( - setVariationLogMessage, - sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') - ); - - assert.strictEqual( - variationIsMappedLogMessage, - sprintf(USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', 'control', 'testExperiment', 'user1') - ); - - assert.strictEqual( - variationMappingRemovedLogMessage, - sprintf(VARIATION_REMOVED_FOR_USER, 'DECISION_SERVICE', 'testExperiment', 'user1') - ); }); it('should be able to set multiple experiments for one user', function() { @@ -2102,28 +1936,11 @@ describe('lib/optimizely', function() { 'definitely_not_valid_variation_key' ); assert.strictEqual(didSetVariation, false); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf( - NO_VARIATION_FOR_EXPERIMENT_KEY, - 'DECISION_SERVICE', - 'definitely_not_valid_variation_key', - 'testExperiment' - ) - ); }); it('should not set an invalid experiment', function() { var didSetVariation = optlyInstance.setForcedVariation('definitely_not_valid_exp_key', 'user1', 'control'); assert.strictEqual(didSetVariation, false); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf(EXPERIMENT_KEY_NOT_IN_DATAFILE, 'PROJECT_CONFIG', 'definitely_not_valid_exp_key') - ); }); it('should return null for user has no forced variation for experiment', function() { @@ -2132,78 +1949,61 @@ describe('lib/optimizely', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperimentLaunched', 'user1'); assert.strictEqual(forcedVariation, null); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') - ); - - var noVariationToGetLogMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - noVariationToGetLogMessage, - sprintf( - USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - 'DECISION_SERVICE', - 'testExperimentLaunched', - 'user1' - ) - ); }); it('should return false for a null experimentKey', function() { var didSetVariation = optlyInstance.setForcedVariation(null, 'user1', 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + // ); }); it('should return false for an undefined experimentKey', function() { var didSetVariation = optlyInstance.setForcedVariation(undefined, 'user1', 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + // ); }); it('should return false for an empty experimentKey', function() { var didSetVariation = optlyInstance.setForcedVariation('', 'user1', 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + // ); }); it('should return false for a null userId', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', null, 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') + // ); }); it('should return false for an undefined userId', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', undefined, 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') + // ); }); it('should return true for an empty userId', function() { @@ -2214,23 +2014,11 @@ describe('lib/optimizely', function() { it('should return false for a null variationKey', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'user1', null); assert.strictEqual(didSetVariation, false); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); }); it('should return false for an undefined variationKey', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'user1', undefined); assert.strictEqual(didSetVariation, false); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); }); it('should not override check for not running experiments in getVariation', function() { @@ -2243,18 +2031,6 @@ describe('lib/optimizely', function() { var variation = optlyInstance.getVariation('testExperimentNotRunning', 'user1', {}); assert.strictEqual(variation, null); - - var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage0, - sprintf(USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 133338, 133337, 'user1') - ); - - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage1, - sprintf(EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') - ); }); }); @@ -2263,7 +2039,7 @@ describe('lib/optimizely', function() { assert.isTrue(optlyInstance.validateInputs({ user_id: 'testUser' })); assert.isTrue(optlyInstance.validateInputs({ user_id: '' })); assert.isTrue(optlyInstance.validateInputs({ user_id: 'testUser' }, { browser_type: 'firefox' })); - sinon.assert.notCalled(createdLogger.log); + // sinon.assert.notCalled(createdLogger.log); }); it('should return false and throw an error if user ID is invalid', function() { @@ -2276,26 +2052,31 @@ describe('lib/optimizely', function() { falseUserIdInput = optlyInstance.validateInputs({ user_id: 3.14 }); assert.isFalse(falseUserIdInput); - sinon.assert.calledThrice(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledThrice(errorHandler.handleError); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - sinon.assert.calledThrice(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledThrice(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should return false and throw an error if attributes are invalid', function() { + const { optlyInstance, errorNotifier} = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + var falseUserIdInput = optlyInstance.validateInputs({ user_id: 'testUser' }, []); assert.isFalse(falseUserIdInput); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + sinon.assert.calledOnce(errorNotifier.notify); + + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); }); @@ -3071,10 +2852,8 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, false); sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Feature %s is not enabled for user %s.', - 'OPTIMIZELY', + createdLogger.info, + FEATURE_NOT_ENABLED_FOR_USER, 'test_feature', 'user1' ); @@ -4521,7 +4300,7 @@ describe('lib/optimizely', function() { describe('decide APIs', function() { var optlyInstance; var bucketStub; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -4552,7 +4331,10 @@ describe('lib/optimizely', function() { bucketStub = sinon.stub(bucketer, 'bucket'); sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); }); @@ -4560,7 +4342,10 @@ describe('lib/optimizely', function() { eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); errorHandler.handleError.restore(); - createdLogger.log.restore(); + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); }); @@ -4619,23 +4404,29 @@ describe('lib/optimizely', function() { }); it('should call the error handler for invalid user ID and return null', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); assert.isNull(optlyInstance.createUserContext(1)); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should call the error handler for invalid attributes and return null', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); assert.isNull(optlyInstance.createUserContext('user1', 'invalid_attributes')); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); }); @@ -4651,14 +4442,20 @@ describe('lib/optimizely', function() { sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); }); afterEach(function() { eventDispatcher.dispatchEvent.reset(); errorHandler.handleError.restore(); - createdLogger.log.restore(); + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); notificationCenter.sendNotifications.restore(); }); @@ -4705,6 +4502,15 @@ describe('lib/optimizely', function() { }); it('should make a decision for feature_test and dispatch an event', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4800,6 +4606,15 @@ describe('lib/optimizely', function() { }); it('should make a decision and do not dispatch an event with DISABLE_DECISION_EVENT passed in decide options', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4841,6 +4656,15 @@ describe('lib/optimizely', function() { }); it('should make a decision with excluded variables and do not dispatch an event with DISABLE_DECISION_EVENT and EXCLUDE_VARIABLES passed in decide options', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -4884,11 +4708,15 @@ describe('lib/optimizely', function() { }); it('should make a decision for rollout and dispatch an event when sendFlagDecisions is set to true', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance( + const { optlyInstance, eventDispatcher } = getOptlyInstance( { datafileObj: testData.getTestDecideProjectConfig(), } - ) + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_1'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4930,11 +4758,14 @@ describe('lib/optimizely', function() { }); it('should make a decision for rollout and do not dispatch an event when sendFlagDecisions is set to false', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance( + const { optlyInstance, eventDispatcher } = getOptlyInstance( { datafileObj: testData.getTestDecideProjectConfig(), } - ) + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = false; @@ -4980,11 +4811,15 @@ describe('lib/optimizely', function() { }); it('should make a decision when variation is null and dispatch an event', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance( + const { optlyInstance, eventDispatcher } = getOptlyInstance( { datafileObj: testData.getTestDecideProjectConfig(), } - ) + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_3'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -5028,10 +4863,14 @@ describe('lib/optimizely', function() { describe('with EXCLUDE_VARIABLES flag in default decide options', function() { it('should exclude variables in decision object and dispatch an event', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], - }) + }); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -5072,11 +4911,14 @@ describe('lib/optimizely', function() { }); it('should exclude variables in decision object and do not dispatch an event when DISABLE_DECISION_EVENT is passed in decide options', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], }) + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -5319,6 +5161,7 @@ describe('lib/optimizely', function() { userId, }); var decision = optlyInstance.decide(user, flagKey); + expect(decision.reasons).to.include( sprintf(FORCED_BUCKETING_FAILED, 'DECISION_SERVICE', variationKey, userId) ); @@ -5816,6 +5659,7 @@ describe('lib/optimizely', function() { it('should return decision results map with single flag key provided for feature_test and dispatch an event', function() { var flagKey = 'feature_2'; const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); + sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); var user = optlyInstance.createUserContext(userId); var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); @@ -6210,7 +6054,7 @@ describe('lib/optimizely', function() { //tests separated out from APIs because of mock bucketing describe('getVariationBucketingIdAttribute', function() { var optlyInstance; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -6277,7 +6121,7 @@ describe('lib/optimizely', function() { describe('feature management', function() { var sandbox = sinon.sandbox.create(); - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -6340,10 +6184,10 @@ describe('lib/optimizely', function() { }); var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', 'user1'); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Optimizely object is not valid. Failing isFeatureEnabled.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Optimizely object is not valid. Failing isFeatureEnabled.' + // ); }); describe('when the user bucketed into a variation of an experiment with the feature', function() { @@ -6443,65 +6287,65 @@ describe('lib/optimizely', function() { }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature_for_experiment is enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature_for_experiment is enabled for user user1.' + // ); }); it('returns false and does not dispatch an impression event when feature key is null', function() { var result = optlyInstance.isFeatureEnabled(null, 'user1', attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided feature_key is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided feature_key is in an invalid format.' + // ); }); it('returns false when user id is null', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', null, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key and user id are null', function() { var result = optlyInstance.isFeatureEnabled(null, null, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key is undefined', function() { var result = optlyInstance.isFeatureEnabled(undefined, 'user1', attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided feature_key is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided feature_key is in an invalid format.' + // ); }); it('returns false when user id is undefined', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', undefined, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key and user id are undefined', function() { @@ -6514,44 +6358,44 @@ describe('lib/optimizely', function() { var result = optlyInstance.isFeatureEnabled(); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when user id is an object', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', {}, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when user id is a number', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', 72, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key is an array', function() { var result = optlyInstance.isFeatureEnabled(['a', 'feature'], 'user1', attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided feature_key is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided feature_key is in an invalid format.' + // ); }); it('returns true when user id is an empty string', function() { @@ -6729,10 +6573,10 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, true); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is enabled for user user1.' + // ); }); }); @@ -6758,10 +6602,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' + // ); }); }); }); @@ -6787,10 +6631,10 @@ describe('lib/optimizely', function() { var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' + // ); }); it('returns false and does not dispatch an event when sendFlagDecisions is set to false', function() { @@ -6800,10 +6644,10 @@ describe('lib/optimizely', function() { var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' + // ); }); it('returns false and dispatch an event when sendFlagDecisions is set to true', function() { @@ -6895,10 +6739,10 @@ describe('lib/optimizely', function() { }); var result = optlyInstance.getEnabledFeatures('user1', { test_attribute: 'test_value' }); assert.deepEqual(result, []); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Optimizely object is not valid. Failing getEnabledFeatures.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Optimizely object is not valid. Failing getEnabledFeatures.' + // ); }); it('returns only enabled features for the specified user and attributes', function() { @@ -7067,10 +6911,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is double', function() { @@ -7078,10 +6922,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 20.25); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is integer', function() { @@ -7089,10 +6933,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 2); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is string', function() { @@ -7100,10 +6944,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me NOW'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is json', function() { @@ -7114,10 +6958,10 @@ describe('lib/optimizely', function() { num_buttons: 1, text: 'first variation', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableBoolean', function() { @@ -7128,10 +6972,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableDouble', function() { @@ -7142,10 +6986,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 20.25); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableInteger', function() { @@ -7156,10 +7000,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 2); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableString', function() { @@ -7167,10 +7011,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me NOW'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableJSON', function() { @@ -7181,10 +7025,10 @@ describe('lib/optimizely', function() { num_buttons: 1, text: 'first variation', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7201,51 +7045,51 @@ describe('lib/optimizely', function() { text: 'first variation', }, }); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '2', - 'num_buttons', - 'test_feature_for_experiment' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'true', - 'is_button_animated', - 'test_feature_for_experiment' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'Buy me NOW', - 'button_txt', - 'test_feature_for_experiment' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '20.25', - 'button_width', - 'test_feature_for_experiment' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '{ "num_buttons": 1, "text": "first variation"}', - 'button_info', - 'test_feature_for_experiment' - ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '2', + // 'num_buttons', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'true', + // 'is_button_animated', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'Buy me NOW', + // 'button_txt', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '20.25', + // 'button_width', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '{ "num_buttons": 1, "text": "first variation"}', + // 'button_info', + // 'test_feature_for_experiment' + // ); }); describe('when the variable is not used in the variation', function() { @@ -7261,10 +7105,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -7272,10 +7116,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -7283,10 +7127,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -7294,10 +7138,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -7308,10 +7152,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -7322,10 +7166,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -7336,10 +7180,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -7350,10 +7194,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -7364,10 +7208,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -7378,10 +7222,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7398,46 +7242,46 @@ describe('lib/optimizely', function() { text: 'default value', }, }); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'num_buttons', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'is_button_animated', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'button_txt', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'button_width', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'button_info', - 'variation' - ); + // sinon.assert.calledWith( + // createdLogger.info, + // // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'num_buttons', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'is_button_animated', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'button_txt', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'button_width', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'button_info', + // 'variation' + // ); }); }); }); @@ -7469,10 +7313,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -7480,10 +7324,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -7491,10 +7335,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -7502,10 +7346,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -7516,10 +7360,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -7530,10 +7374,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -7544,10 +7388,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -7558,10 +7402,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -7569,10 +7413,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -7583,10 +7427,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7603,48 +7447,48 @@ describe('lib/optimizely', function() { text: 'default value', }, }); - assert.deepEqual(createdLogger.log.args, [ - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - '10', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - 'false', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - 'Buy me', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - '50.55', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - '{ "num_buttons": 0, "text": "default value"}', - ], - ]); + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // '10', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // 'false', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // 'Buy me', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // '50.55', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // '{ "num_buttons": 0, "text": "default value"}', + // ], + // ]); }); }); }); @@ -7674,10 +7518,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is double', function() { @@ -7685,10 +7529,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 4.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is integer', function() { @@ -7696,10 +7540,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 395); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is string', function() { @@ -7707,10 +7551,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello audience'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is json', function() { @@ -7721,10 +7565,10 @@ describe('lib/optimizely', function() { count: 2, message: 'Hello audience', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableBoolean', function() { @@ -7732,10 +7576,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableDouble', function() { @@ -7743,10 +7587,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 4.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableInteger', function() { @@ -7754,10 +7598,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 395); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableString', function() { @@ -7765,10 +7609,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello audience'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableJSON', function() { @@ -7779,10 +7623,10 @@ describe('lib/optimizely', function() { count: 2, message: 'Hello audience', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7799,48 +7643,48 @@ describe('lib/optimizely', function() { message: 'Hello audience', }, }); - assert.deepEqual(createdLogger.log.args, [ - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'true', - 'new_content', - 'test_feature', - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '395', - 'lasers', - 'test_feature', - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '4.99', - 'price', - 'test_feature', - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'Hello audience', - 'message', - 'test_feature', - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '{ "count": 2, "message": "Hello audience" }', - 'message_info', - 'test_feature', - ], - ]); + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'true', + // 'new_content', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '395', + // 'lasers', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '4.99', + // 'price', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'Hello audience', + // 'message', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '{ "count": 2, "message": "Hello audience" }', + // 'message_info', + // 'test_feature', + // ], + // ]); }); describe('when the variable is not used in the variation', function() { @@ -7853,10 +7697,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -7864,10 +7708,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -7875,10 +7719,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -7886,10 +7730,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -7900,10 +7744,10 @@ describe('lib/optimizely', function() { count: 1, message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -7911,10 +7755,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -7922,10 +7766,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -7933,10 +7777,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -7944,10 +7788,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -7958,10 +7802,10 @@ describe('lib/optimizely', function() { count: 1, message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7978,43 +7822,43 @@ describe('lib/optimizely', function() { message: 'Hello', }, }); - assert.deepEqual(createdLogger.log.args, [ - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'new_content', - '594032', - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'lasers', - '594032', - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'price', - '594032', - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'message', - '594032', - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'message_info', - '594032', - ], - ]); + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'new_content', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'lasers', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'price', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'message', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'message_info', + // '594032', + // ], + // ]); }); }); }); @@ -8043,10 +7887,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -8054,10 +7898,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -8065,10 +7909,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -8076,10 +7920,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -8090,10 +7934,10 @@ describe('lib/optimizely', function() { count: 1, message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -8101,10 +7945,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -8112,10 +7956,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -8123,10 +7967,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -8134,10 +7978,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -8148,10 +7992,10 @@ describe('lib/optimizely', function() { count: 1, message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -8168,48 +8012,48 @@ describe('lib/optimizely', function() { message: 'Hello', }, }); - assert.deepEqual(createdLogger.log.args, [ - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - 'false', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '400', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '14.99', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - 'Hello', - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '{ "count": 1, "message": "Hello" }', - ], - ]); + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // 'false', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // '400', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // '14.99', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // 'Hello', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // '{ "count": 1, "message": "Hello" }', + // ], + // ]); }); }); }); @@ -8233,10 +8077,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -8244,10 +8088,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -8255,10 +8099,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -8266,10 +8110,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -8280,10 +8124,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -8294,10 +8138,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -8305,10 +8149,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -8316,10 +8160,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -8327,10 +8171,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -8341,10 +8185,11 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' - ); + + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -8361,48 +8206,48 @@ describe('lib/optimizely', function() { text: 'default value', }, }); - assert.deepEqual(createdLogger.log.args, [ - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'num_buttons', - 'test_feature_for_experiment', - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'is_button_animated', - 'test_feature_for_experiment', - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_txt', - 'test_feature_for_experiment', - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_width', - 'test_feature_for_experiment', - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_info', - 'test_feature_for_experiment', - ], - ]); + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'num_buttons', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'is_button_animated', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'button_txt', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'button_width', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'button_info', + // 'test_feature_for_experiment', + // ], + // ]); }); }); @@ -8411,10 +8256,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is boolean', function() { @@ -8422,19 +8267,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'is_button_animated'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is double', function() { @@ -8442,10 +8287,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is double', function() { @@ -8453,19 +8298,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is integer', function() { @@ -8473,10 +8318,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is integer', function() { @@ -8484,19 +8329,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is string', function() { @@ -8504,10 +8349,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is string', function() { @@ -8515,19 +8360,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is json', function() { @@ -8535,10 +8380,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is json', function() { @@ -8546,28 +8391,28 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is json', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableBoolean when called with a non-boolean variable', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "boolean", but variable is of type "double". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "boolean", but variable is of type "double". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableDouble when called with a non-double variable', function() { @@ -8577,37 +8422,37 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "double", but variable is of type "boolean". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "double", but variable is of type "boolean". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableInteger when called with a non-integer variable', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "integer", but variable is of type "double". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "integer", but variable is of type "double". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableString when called with a non-string variable', function() { var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "string", but variable is of type "integer". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "string", but variable is of type "integer". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableJSON when called with a non-json variable', function() { var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_txt', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "json", but variable is of type "string". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "json", but variable is of type "string". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableBoolean if user id is null', function() { @@ -8618,10 +8463,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableBoolean if user id is undefined', function() { @@ -8632,19 +8477,19 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableBoolean if user id is not provided', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableDouble if user id is null', function() { @@ -8652,10 +8497,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableDouble if user id is undefined', function() { @@ -8663,19 +8508,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableDouble if user id is not provided', function() { var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableInteger if user id is null', function() { @@ -8683,10 +8528,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableInteger if user id is undefined', function() { @@ -8694,19 +8539,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableInteger if user id is not provided', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableString if user id is null', function() { @@ -8714,10 +8559,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableString if user id is undefined', function() { @@ -8725,19 +8570,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableString if user id is not provided', function() { var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableJSON if user id is null', function() { @@ -8745,10 +8590,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableJSON if user id is undefined', function() { @@ -8756,19 +8601,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableJSON if user id is not provided', function() { var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_info'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); describe('type casting failures', function() { @@ -8784,10 +8629,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value falsezzz to type boolean, returning null.' - ); }); }); @@ -8799,10 +8640,6 @@ describe('lib/optimizely', function() { it('should return null and log an error', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value zzz123 to type integer, returning null.' - ); }); }); @@ -8814,10 +8651,6 @@ describe('lib/optimizely', function() { it('should return null and log an error', function() { var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value zzz44.55 to type double, returning null.' - ); }); }); @@ -8829,10 +8662,6 @@ describe('lib/optimizely', function() { it('should return null and log an error', function() { var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_info', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value zzz44.55 to type json, returning null.' - ); }); }); }); @@ -8840,46 +8669,26 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is double', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is string', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_txt', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is json', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_info', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument variable key is invalid', function() { @@ -8889,55 +8698,31 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableBoolean if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableBoolean('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableDouble if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableDouble('thisIsNotAValidKey<><><>', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableInteger if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableInteger('thisIsNotAValidKey<><><>', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableString if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableString('thisIsNotAValidKey<><><>', 'button_txt', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableJSON if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableJSON('thisIsNotAValidKey<><><>', 'button_info', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableBoolean if the argument variable key is invalid', function() { @@ -8947,10 +8732,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableDouble if the argument variable key is invalid', function() { @@ -8960,10 +8741,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableInteger if the argument variable key is invalid', function() { @@ -8973,10 +8750,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableString if the argument variable key is invalid', function() { @@ -8986,10 +8759,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableJSON if the argument variable key is invalid', function() { @@ -8999,29 +8768,17 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariable when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), }); - createdLogger.log.reset(); + sinon.stub(createdLogger, 'error'); - instance.getFeatureVariable('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariable')); + const val = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableBoolean when optimizely object is not a valid instance', function() { @@ -9034,13 +8791,8 @@ describe('lib/optimizely', function() { eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableBoolean('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableBoolean')); + const val = instance.getFeatureVariableBoolean('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableDouble when optimizely object is not a valid instance', function() { @@ -9053,13 +8805,8 @@ describe('lib/optimizely', function() { eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableDouble('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableDouble')); + const val = instance.getFeatureVariableDouble('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableInteger when optimizely object is not a valid instance', function() { @@ -9072,13 +8819,8 @@ describe('lib/optimizely', function() { eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableInteger('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableInteger')); + const val = instance.getFeatureVariableInteger('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableString when optimizely object is not a valid instance', function() { @@ -9091,13 +8833,8 @@ describe('lib/optimizely', function() { eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableString('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableString')); + const val = instance.getFeatureVariableString('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableJSON when optimizely object is not a valid instance', function() { @@ -9110,20 +8847,15 @@ describe('lib/optimizely', function() { eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableJSON('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableJSON')); + const val = instance.getFeatureVariableJSON('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); }); }); describe('audience match types', function() { var sandbox = sinon.sandbox.create(); - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -9268,7 +9000,7 @@ describe('lib/optimizely', function() { describe('audience combinations', function() { var sandbox = sinon.sandbox.create(); var evalSpy; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -9470,7 +9202,7 @@ describe('lib/optimizely', function() { var eventDispatcher; var eventProcessor; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -9478,7 +9210,10 @@ describe('lib/optimizely', function() { beforeEach(function() { bucketStub = sinon.stub(bucketer, 'bucket'); sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); eventDispatcher = getMockEventDispatcher(); @@ -9491,299 +9226,13 @@ describe('lib/optimizely', function() { eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); errorHandler.handleError.restore(); - createdLogger.log.restore(); + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); }); - // TODO: these tests does not belong here, these belong in EventProcessor tests - // describe('when eventBatchSize = 3 and eventFlushInterval = 100', function() { - // var optlyInstance; - - // beforeEach(function() { - // const mockConfigManager = getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestProjectConfig()), - // }); - - // optlyInstance = new Optimizely({ - // clientEngine: 'node-sdk', - // projectConfigManager: mockConfigManager, - // errorHandler: errorHandler, - // eventProcessor, - // jsonSchemaValidator: jsonSchemaValidator, - // logger: createdLogger, - // isValidInstance: true, - // eventBatchSize: 3, - // eventFlushInterval: 100, - // eventProcessor, - // notificationCenter, - // }); - // }); - - // afterEach(function() { - // optlyInstance.close(); - // }); - - // it('should send batched events when the maxQueueSize is reached', function() { - // fakeDecisionResponse = { - // result: '111129', - // reasons: [], - // }; - // bucketStub.returns(fakeDecisionResponse); - // var activate = optlyInstance.activate('testExperiment', 'testUser'); - // assert.strictEqual(activate, 'variation'); - - // optlyInstance.track('testEvent', 'testUser'); - // optlyInstance.track('testEvent', 'testUser'); - - // sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - // var expectedObj = { - // url: 'https://logx.optimizely.com/v1/events', - // httpVerb: 'POST', - // params: { - // account_id: '12001', - // project_id: '111001', - // visitors: [ - // { - // snapshots: [ - // { - // decisions: [ - // { - // campaign_id: '4', - // experiment_id: '111127', - // variation_id: '111129', - // metadata: { - // flag_key: '', - // rule_key: 'testExperiment', - // rule_type: 'experiment', - // variation_key: 'variation', - // enabled: true, - // }, - // }, - // ], - // events: [ - // { - // entity_id: '4', - // timestamp: Math.round(new Date().getTime()), - // key: 'campaign_activated', - // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - // }, - // ], - // }, - // ], - // visitor_id: 'testUser', - // attributes: [], - // }, - // { - // attributes: [], - // snapshots: [ - // { - // events: [ - // { - // entity_id: '111095', - // key: 'testEvent', - // timestamp: new Date().getTime(), - // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - // }, - // ], - // }, - // ], - // visitor_id: 'testUser', - // }, - // { - // attributes: [], - // snapshots: [ - // { - // events: [ - // { - // entity_id: '111095', - // key: 'testEvent', - // timestamp: new Date().getTime(), - // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - // }, - // ], - // }, - // ], - // visitor_id: 'testUser', - // }, - // ], - // revision: '42', - // client_name: 'node-sdk', - // client_version: enums.CLIENT_VERSION, - // anonymize_ip: false, - // enrich_decisions: true, - // }, - // }; - // var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - // assert.deepEqual(eventDispatcherCall[0], expectedObj); - // }); - - // it('should flush the queue when the flushInterval occurs', function() { - // var timestamp = new Date().getTime(); - // fakeDecisionResponse = { - // result: '111129', - // reasons: [], - // }; - // bucketStub.returns(fakeDecisionResponse); - // var activate = optlyInstance.activate('testExperiment', 'testUser'); - // assert.strictEqual(activate, 'variation'); - - // optlyInstance.track('testEvent', 'testUser'); - - // sinon.assert.notCalled(eventDispatcher.dispatchEvent); - - // clock.tick(100); - - // sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - // var expectedObj = { - // url: 'https://logx.optimizely.com/v1/events', - // httpVerb: 'POST', - // params: { - // account_id: '12001', - // project_id: '111001', - // visitors: [ - // { - // snapshots: [ - // { - // decisions: [ - // { - // campaign_id: '4', - // experiment_id: '111127', - // variation_id: '111129', - // metadata: { - // flag_key: '', - // rule_key: 'testExperiment', - // rule_type: 'experiment', - // variation_key: 'variation', - // enabled: true, - // }, - // }, - // ], - // events: [ - // { - // entity_id: '4', - // timestamp: timestamp, - // key: 'campaign_activated', - // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - // }, - // ], - // }, - // ], - // visitor_id: 'testUser', - // attributes: [], - // }, - // { - // attributes: [], - // snapshots: [ - // { - // events: [ - // { - // entity_id: '111095', - // key: 'testEvent', - // timestamp: timestamp, - // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - // }, - // ], - // }, - // ], - // visitor_id: 'testUser', - // }, - // ], - // revision: '42', - // client_name: 'node-sdk', - // client_version: enums.CLIENT_VERSION, - // anonymize_ip: false, - // enrich_decisions: true, - // }, - // }; - // var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - // assert.deepEqual(eventDispatcherCall[0], expectedObj); - // }); - - // it('should flush the queue when optimizely.close() is called', function() { - // fakeDecisionResponse = { - // result: '111129', - // reasons: [], - // }; - // bucketStub.returns(fakeDecisionResponse); - // var activate = optlyInstance.activate('testExperiment', 'testUser'); - // assert.strictEqual(activate, 'variation'); - - // optlyInstance.track('testEvent', 'testUser'); - - // sinon.assert.notCalled(eventDispatcher.dispatchEvent); - - // optlyInstance.close(); - - // sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - // var expectedObj = { - // url: 'https://logx.optimizely.com/v1/events', - // httpVerb: 'POST', - // params: { - // account_id: '12001', - // project_id: '111001', - // visitors: [ - // { - // snapshots: [ - // { - // decisions: [ - // { - // campaign_id: '4', - // experiment_id: '111127', - // variation_id: '111129', - // metadata: { - // flag_key: '', - // rule_key: 'testExperiment', - // rule_type: 'experiment', - // variation_key: 'variation', - // enabled: true, - // }, - // }, - // ], - // events: [ - // { - // entity_id: '4', - // timestamp: Math.round(new Date().getTime()), - // key: 'campaign_activated', - // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - // }, - // ], - // }, - // ], - // visitor_id: 'testUser', - // attributes: [], - // }, - // { - // attributes: [], - // snapshots: [ - // { - // events: [ - // { - // entity_id: '111095', - // key: 'testEvent', - // timestamp: new Date().getTime(), - // uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - // }, - // ], - // }, - // ], - // visitor_id: 'testUser', - // }, - // ], - // revision: '42', - // client_name: 'node-sdk', - // client_version: enums.CLIENT_VERSION, - // anonymize_ip: false, - // enrich_decisions: true, - // }, - // }; - // var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - // assert.deepEqual(eventDispatcherCall[0], expectedObj); - // }); - // }); - describe('close method', function() { var eventProcessorStopPromise; var optlyInstance; @@ -9879,7 +9328,7 @@ describe('lib/optimizely', function() { }); describe('project config management', function() { - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); @@ -9892,13 +9341,19 @@ describe('lib/optimizely', function() { beforeEach(function() { sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); }); afterEach(function() { + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); eventDispatcher.dispatchEvent.reset(); errorHandler.handleError.restore(); - createdLogger.log.restore(); }); var optlyInstance; @@ -10205,9 +9660,9 @@ describe('lib/optimizely', function() { var bucketStub; var fakeDecisionResponse; var eventDispatcherSpy; - var logger = { log: function() {} }; + var logger =createLogger(); var errorHandler = { handleError: function() {} }; - var notificationCenter = createNotificationCenter({ logger, errorHandler }); + var notificationCenter = createNotificationCenter({ logger }); var eventProcessor; beforeEach(function() { bucketStub = sinon.stub(bucketer, 'bucket'); @@ -10267,85 +9722,4 @@ describe('lib/optimizely', function() { sinon.assert.calledWithExactly(notificationListener, eventDispatcherSpy.getCall(0).args[0]); }); }); - - // Note: /lib/index.browser.tests.js contains relevant Opti Client x Browser ODP Tests - // TODO: Finish these tests in ODP Node.js Implementation - describe('odp', () => { - // var optlyInstanceWithOdp; - // var bucketStub; - // var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); - // var eventDispatcher = getMockEventDispatcher(); - // var eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); - // var createdLogger = logger.createLogger({ - // logLevel: LOG_LEVEL.INFO, - // logToConsole: false, - // }); - - // beforeEach(function() { - // const datafile = testData.getTestProjectConfig(); - // const mockConfigManager = getMockProjectConfigManager(); - // mockConfigManager.setConfig(createProjectConfig(datafile, JSON.stringify(datafile))); - - // optlyInstanceWithOdp = new Optimizely({ - // clientEngine: 'node-sdk', - // projectConfigManager: mockConfigManager, - // errorHandler: errorHandler, - // eventDispatcher: eventDispatcher, - // jsonSchemaValidator: jsonSchemaValidator, - // logger: createdLogger, - // isValidInstance: true, - // eventBatchSize: 1, - // eventProcessor, - // notificationCenter, - // odpManager: new NodeOdpManager({}), - // }); - - // bucketStub = sinon.stub(bucketer, 'bucket'); - // sinon.stub(errorHandler, 'handleError'); - // sinon.stub(createdLogger, 'log'); - // sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - // }); - - // afterEach(function() { - // eventDispatcher.dispatchEvent.reset(); - // bucketer.bucket.restore(); - // errorHandler.handleError.restore(); - // createdLogger.log.restore(); - // fns.uuid.restore(); - // }); - - it('should send an identify event when called with odp enabled', () => { - // TODO - }); - - it('should flush the odp event queue as part of the close() function call', () => { - // TODO - }); - - describe('odp manager overrides', () => { - it('should accept custom cache size and timeout overrides defined in odp service config', () => { - // TODO - }); - - it('should accept a valid custom cache', () => { - // TODO - }); - - it('should call logger with log level of "error" when custom cache is invalid', () => { - // TODO - }); - - it('should accept a custom segment mananger override defined in odp service config', () => { - // TODO - }); - - it('should accept a custom event manager override defined in odp service config', () => { - // TODO - }); - - it('should call logger with log level of "error" when odp service config is invalid', () => { - // TODO - }); - }); - }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index d71abfd3a..1d30e4fa1 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { LoggerFacade, ErrorHandler } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger'; import { sprintf, objectValues } from '../utils/fns'; -import { DefaultNotificationCenter, NotificationCenter } from '../notification_center'; +import { createNotificationCenter, DefaultNotificationCenter, NotificationCenter } from '../notification_center'; import { EventProcessor } from '../event_processor/event_processor'; import { OdpManager } from '../odp/odp_manager'; @@ -74,29 +73,32 @@ import { ODP_EVENT_FAILED, ODP_EVENT_FAILED_ODP_MANAGER_MISSING, UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE, + UNRECOGNIZED_DECIDE_OPTION, + INVALID_OBJECT, + EVENT_KEY_NOT_FOUND, + NOT_TRACKING_USER, + VARIABLE_REQUESTED_WITH_WRONG_TYPE, } from '../error_messages'; + import { - EVENT_KEY_NOT_FOUND, FEATURE_ENABLED_FOR_USER, FEATURE_NOT_ENABLED_FOR_USER, FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, INVALID_CLIENT_ENGINE, INVALID_DECIDE_OPTIONS, INVALID_DEFAULT_DECIDE_OPTIONS, - INVALID_OBJECT, NOT_ACTIVATING_USER, - NOT_TRACKING_USER, SHOULD_NOT_DISPATCH_ACTIVATE, TRACK_EVENT, - UNRECOGNIZED_DECIDE_OPTION, UPDATED_OPTIMIZELY_CONFIG, USER_RECEIVED_DEFAULT_VARIABLE_VALUE, USER_RECEIVED_VARIABLE_VALUE, VALID_USER_PROFILE_SERVICE, VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, - VARIABLE_REQUESTED_WITH_WRONG_TYPE, } from '../log_messages'; import { INSTANCE_CLOSED } from '../exception_messages'; +import { ErrorNotifier } from '../error/error_notifier'; +import { ErrorReporter } from '../error/error_reporter'; const MODULE_NAME = 'OPTIMIZELY'; @@ -110,7 +112,6 @@ type StringInputs = Partial<Record<InputKey, unknown>>; type DecisionReasons = (string | number)[]; export default class Optimizely implements Client { - private isOptimizelyConfigValid: boolean; private disposeOnUpdate?: Fn; private readyPromise: Promise<unknown>; // readyTimeout is specified as any to make this work in both browser & Node @@ -119,8 +120,9 @@ export default class Optimizely implements Client { private nextReadyTimeoutId: number; private clientEngine: string; private clientVersion: string; - private errorHandler: ErrorHandler; - private logger: LoggerFacade; + private errorNotifier?: ErrorNotifier; + private errorReporter: ErrorReporter; + protected logger?: LoggerFacade; private projectConfigManager: ProjectConfigManager; private decisionService: DecisionService; private eventProcessor?: EventProcessor; @@ -132,17 +134,16 @@ export default class Optimizely implements Client { constructor(config: OptimizelyOptions) { let clientEngine = config.clientEngine; if (!clientEngine) { - config.logger.log(LOG_LEVEL.INFO, INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine); + config.logger?.info(INVALID_CLIENT_ENGINE, clientEngine); clientEngine = NODE_CLIENT_ENGINE; } this.clientEngine = clientEngine; this.clientVersion = config.clientVersion || CLIENT_VERSION; - this.errorHandler = config.errorHandler; - this.isOptimizelyConfigValid = config.isValidInstance; + this.errorNotifier = config.errorNotifier; this.logger = config.logger; this.projectConfigManager = config.projectConfigManager; - this.notificationCenter = config.notificationCenter; + this.errorReporter = new ErrorReporter(this.logger, this.errorNotifier); this.odpManager = config.odpManager; this.vuidManager = config.vuidManager; this.eventProcessor = config.eventProcessor; @@ -156,7 +157,7 @@ export default class Optimizely implements Client { let decideOptionsArray = config.defaultDecideOptions ?? []; if (!Array.isArray(decideOptionsArray)) { - this.logger.log(LOG_LEVEL.DEBUG, INVALID_DEFAULT_DECIDE_OPTIONS, MODULE_NAME); + this.logger?.debug(INVALID_DEFAULT_DECIDE_OPTIONS); decideOptionsArray = []; } @@ -166,16 +167,14 @@ export default class Optimizely implements Client { if (OptimizelyDecideOption[option]) { defaultDecideOptions[option] = true; } else { - this.logger.log(LOG_LEVEL.WARNING, UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); + this.logger?.warn(UNRECOGNIZED_DECIDE_OPTION, option); } }); this.defaultDecideOptions = defaultDecideOptions; this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( UPDATED_OPTIMIZELY_CONFIG, - MODULE_NAME, configObj.revision, configObj.projectId ); @@ -193,10 +192,10 @@ export default class Optimizely implements Client { try { if (userProfileServiceValidator.validate(config.userProfileService)) { userProfileService = config.userProfileService; - this.logger.log(LOG_LEVEL.INFO, VALID_USER_PROFILE_SERVICE, MODULE_NAME); + this.logger?.info(VALID_USER_PROFILE_SERVICE); } } catch (ex) { - this.logger.log(LOG_LEVEL.WARNING, ex.message); + this.logger?.warn(ex); } } @@ -206,6 +205,10 @@ export default class Optimizely implements Client { UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, }); + this.notificationCenter = createNotificationCenter({ logger: this.logger, errorNotifier: this.errorNotifier }); + + this.eventProcessor = config.eventProcessor; + this.eventProcessor?.start(); const eventProcessorRunningPromise = this.eventProcessor ? this.eventProcessor.onRunning() : Promise.resolve(undefined); @@ -249,7 +252,7 @@ export default class Optimizely implements Client { * @return {boolean} */ isValidInstance(): boolean { - return this.isOptimizelyConfigValid && !!this.projectConfigManager.getConfig(); + return !!this.projectConfigManager.getConfig(); } /** @@ -262,7 +265,7 @@ export default class Optimizely implements Client { activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'activate'); + this.logger?.error(INVALID_OBJECT, 'activate'); return null; } @@ -283,7 +286,7 @@ export default class Optimizely implements Client { // If experiment is not set to 'Running' status, log accordingly and return variation key if (!projectConfig.isRunning(configObj, experimentKey)) { - this.logger.log(LOG_LEVEL.DEBUG, SHOULD_NOT_DISPATCH_ACTIVATE, MODULE_NAME, experimentKey); + this.logger?.debug(SHOULD_NOT_DISPATCH_ACTIVATE, experimentKey); return variationKey; } @@ -298,14 +301,12 @@ export default class Optimizely implements Client { this.sendImpressionEvent(decisionObj, '', userId, true, attributes); return variationKey; } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.logger.log(LOG_LEVEL.INFO, NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); - this.errorHandler.handleError(ex); + this.logger?.info(NOT_ACTIVATING_USER, userId, experimentKey); + this.errorReporter.report(ex); return null; } } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -328,7 +329,7 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): void { if (!this.eventProcessor) { - this.logger.error(NO_EVENT_PROCESSOR); + this.logger?.error(NO_EVENT_PROCESSOR); return; } @@ -369,12 +370,12 @@ export default class Optimizely implements Client { track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void { try { if (!this.eventProcessor) { - this.logger.error(NO_EVENT_PROCESSOR); + this.logger?.error(NO_EVENT_PROCESSOR); return; } if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'track'); + this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'track'); return; } @@ -387,9 +388,12 @@ export default class Optimizely implements Client { return; } + console.log(eventKey, userId, attributes, eventTags); + if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { - this.logger.log(LOG_LEVEL.WARNING, EVENT_KEY_NOT_FOUND, MODULE_NAME, eventKey); - this.logger.log(LOG_LEVEL.WARNING, NOT_TRACKING_USER, MODULE_NAME, userId); + console.log('eventKey not found',); + this.logger?.warn(EVENT_KEY_NOT_FOUND, eventKey); + this.logger?.warn(NOT_TRACKING_USER, userId); return; } @@ -403,8 +407,8 @@ export default class Optimizely implements Client { clientEngine: this.clientEngine, clientVersion: this.clientVersion, configObj: configObj, - }); - this.logger.log(LOG_LEVEL.INFO, TRACK_EVENT, MODULE_NAME, eventKey, userId); + }, this.logger); + this.logger?.info(TRACK_EVENT, eventKey, userId); // TODO is it okay to not pass a projectConfig as second argument this.eventProcessor.process(conversionEvent); @@ -417,9 +421,8 @@ export default class Optimizely implements Client { logEvent, }); } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - this.logger.log(LOG_LEVEL.ERROR, NOT_TRACKING_USER, MODULE_NAME, userId); + this.errorReporter.report(e); + this.logger?.error(NOT_TRACKING_USER, userId); } } @@ -433,7 +436,7 @@ export default class Optimizely implements Client { getVariation(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getVariation'); + this.logger?.error(INVALID_OBJECT, 'getVariation'); return null; } @@ -449,7 +452,7 @@ export default class Optimizely implements Client { const experiment = configObj.experimentKeyMap[experimentKey]; if (!experiment || experiment.isRollout) { - this.logger.log(LOG_LEVEL.DEBUG, INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey); + this.logger?.debug(INVALID_EXPERIMENT_KEY, experimentKey); return null; } @@ -474,13 +477,11 @@ export default class Optimizely implements Client { return variationKey; } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + this.errorReporter.report(ex); return null; } } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -506,8 +507,7 @@ export default class Optimizely implements Client { try { return this.decisionService.setForcedVariation(configObj, experimentKey, userId, variationKey); } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + this.errorReporter.report(ex); return false; } } @@ -531,8 +531,7 @@ export default class Optimizely implements Client { try { return this.decisionService.getForcedVariation(configObj, experimentKey, userId).result; } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + this.errorReporter.report(ex); return null; } } @@ -568,8 +567,7 @@ export default class Optimizely implements Client { } return true; } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + this.errorReporter.report(ex); return false; } } @@ -581,7 +579,7 @@ export default class Optimizely implements Client { * @return {null} */ private notActivatingExperiment(experimentKey: string, userId: string): null { - this.logger.log(LOG_LEVEL.INFO, NOT_ACTIVATING_USER, MODULE_NAME, userId, experimentKey); + this.logger?.info(NOT_ACTIVATING_USER, userId, experimentKey); return null; } @@ -609,7 +607,7 @@ export default class Optimizely implements Client { isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'isFeatureEnabled'); + this.logger?.error(INVALID_OBJECT, 'isFeatureEnabled'); return false; } @@ -651,9 +649,9 @@ export default class Optimizely implements Client { } if (featureEnabled === true) { - this.logger.log(LOG_LEVEL.INFO, FEATURE_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); + this.logger?.info(FEATURE_ENABLED_FOR_USER, featureKey, userId); } else { - this.logger.log(LOG_LEVEL.INFO, FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId); + this.logger?.info(FEATURE_NOT_ENABLED_FOR_USER, featureKey, userId); featureEnabled = false; } @@ -673,8 +671,7 @@ export default class Optimizely implements Client { return featureEnabled; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return false; } } @@ -690,7 +687,7 @@ export default class Optimizely implements Client { try { const enabledFeatures: string[] = []; if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getEnabledFeatures'); + this.logger?.error(INVALID_OBJECT, 'getEnabledFeatures'); return enabledFeatures; } @@ -711,8 +708,7 @@ export default class Optimizely implements Client { return enabledFeatures; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return []; } } @@ -739,13 +735,12 @@ export default class Optimizely implements Client { ): FeatureVariableValue { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable'); + this.logger?.error(INVALID_OBJECT, 'getFeatureVariable'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, null, userId, attributes); } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -799,10 +794,8 @@ export default class Optimizely implements Client { } if (variableType && variable.type !== variableType) { - this.logger.log( - LOG_LEVEL.WARNING, + this.logger?.warn( VARIABLE_REQUESTED_WITH_WRONG_TYPE, - MODULE_NAME, variableType, variable.type ); @@ -882,38 +875,30 @@ export default class Optimizely implements Client { if (value !== null) { if (featureEnabled) { variableValue = value; - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_RECEIVED_VARIABLE_VALUE, - MODULE_NAME, variableValue, variable.key, featureKey ); } else { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, featureKey, userId, variableValue ); } } else { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, variable.key, variation.key ); } } else { - this.logger.log( - LOG_LEVEL.INFO, + this.logger?.info( USER_RECEIVED_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, userId, variable.key, featureKey @@ -944,7 +929,7 @@ export default class Optimizely implements Client { ): boolean | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean'); + this.logger?.error(INVALID_OBJECT, 'getFeatureVariableBoolean'); return null; } return this.getFeatureVariableForType( @@ -955,8 +940,7 @@ export default class Optimizely implements Client { attributes ) as boolean | null; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -983,7 +967,7 @@ export default class Optimizely implements Client { ): number | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble'); + this.logger?.error(INVALID_OBJECT, 'getFeatureVariableDouble'); return null; } return this.getFeatureVariableForType( @@ -994,8 +978,7 @@ export default class Optimizely implements Client { attributes ) as number | null; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -1022,7 +1005,7 @@ export default class Optimizely implements Client { ): number | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger'); + this.logger?.error(INVALID_OBJECT, 'getFeatureVariableInteger'); return null; } return this.getFeatureVariableForType( @@ -1033,8 +1016,7 @@ export default class Optimizely implements Client { attributes ) as number | null; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -1061,7 +1043,7 @@ export default class Optimizely implements Client { ): string | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); + this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); return null; } return this.getFeatureVariableForType( @@ -1072,8 +1054,7 @@ export default class Optimizely implements Client { attributes ) as string | null; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -1095,13 +1076,12 @@ export default class Optimizely implements Client { getFeatureVariableJSON(featureKey: string, variableKey: string, userId: string, attributes: UserAttributes): unknown { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON'); + this.logger?.error(INVALID_OBJECT, 'getFeatureVariableJSON'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -1123,7 +1103,7 @@ export default class Optimizely implements Client { ): { [variableKey: string]: unknown } | null { try { if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'getAllFeatureVariables'); + this.logger?.error(INVALID_OBJECT, 'getAllFeatureVariables'); return null; } @@ -1183,8 +1163,7 @@ export default class Optimizely implements Client { return allVariables; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -1233,8 +1212,7 @@ export default class Optimizely implements Client { } return this.projectConfigManager.getOptimizelyConfig() || null; } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + this.errorReporter.report(e); return null; } } @@ -1305,8 +1283,7 @@ export default class Optimizely implements Client { } ); } catch (err) { - this.logger.log(LOG_LEVEL.ERROR, err.message); - this.errorHandler.handleError(err); + this.errorReporter.report(err); return Promise.resolve({ success: false, reason: String(err), @@ -1433,7 +1410,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig(); if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.INFO, INVALID_OBJECT, MODULE_NAME, 'decide'); + this.logger?.error(INVALID_OBJECT, 'decide'); return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); } @@ -1448,14 +1425,14 @@ export default class Optimizely implements Client { private getAllDecideOptions(options: OptimizelyDecideOption[]): { [key: string]: boolean } { const allDecideOptions = { ...this.defaultDecideOptions }; if (!Array.isArray(options)) { - this.logger.log(LOG_LEVEL.DEBUG, INVALID_DECIDE_OPTIONS, MODULE_NAME); + this.logger?.debug(INVALID_DECIDE_OPTIONS); } else { options.forEach(option => { // Filter out all provided decide options that are not in OptimizelyDecideOption[] if (OptimizelyDecideOption[option]) { allDecideOptions[option] = true; } else { - this.logger.log(LOG_LEVEL.WARNING, UNRECOGNIZED_DECIDE_OPTION, MODULE_NAME, option); + this.logger?.warn(UNRECOGNIZED_DECIDE_OPTION, option); } }); } @@ -1493,12 +1470,11 @@ export default class Optimizely implements Client { let decisionEventDispatched = false; if (flagEnabled) { - this.logger.log(LOG_LEVEL.INFO, FEATURE_ENABLED_FOR_USER, MODULE_NAME, key, userId); + this.logger?.info(FEATURE_ENABLED_FOR_USER, key, userId); } else { - this.logger.log(LOG_LEVEL.INFO, FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, key, userId); + this.logger?.info(FEATURE_NOT_ENABLED_FOR_USER, key, userId); } - if (!options[OptimizelyDecideOption.EXCLUDE_VARIABLES]) { feature.variables.forEach(variable => { variablesMap[variable.key] = this.getFeatureVariableValueFromVariation( @@ -1579,7 +1555,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig() if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'decideForKeys'); + this.logger?.error(INVALID_OBJECT, 'decideForKeys'); return decisionMap; } if (keys.length === 0) { @@ -1595,7 +1571,7 @@ export default class Optimizely implements Client { for(const key of keys) { const feature = configObj.featureKeyMap[key]; if (!feature) { - this.logger.log(LOG_LEVEL.ERROR, FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key); + this.logger?.error(FEATURE_NOT_IN_DATAFILE, key); decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); continue } @@ -1649,7 +1625,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.ERROR, INVALID_OBJECT, MODULE_NAME, 'decideAll'); + this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'decideAll'); return decisionMap; } @@ -1688,7 +1664,7 @@ export default class Optimizely implements Client { data?: Map<string, unknown> ): void { if (!this.odpManager) { - this.logger.error(ODP_EVENT_FAILED_ODP_MANAGER_MISSING); + this.logger?.error(ODP_EVENT_FAILED_ODP_MANAGER_MISSING); return; } @@ -1696,7 +1672,7 @@ export default class Optimizely implements Client { const odpEvent = new OdpEvent(type || '', action, identifiers, data); this.odpManager.sendEvent(odpEvent); } catch (e) { - this.logger.error(ODP_EVENT_FAILED, e); + this.logger?.error(ODP_EVENT_FAILED, e); } } /** diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index fbc9eb29b..92985fa5a 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -15,13 +15,9 @@ */ import { assert } from 'chai'; import sinon from 'sinon'; - -import * as logging from '../modules/logging'; import { sprintf } from '../utils/fns'; import { NOTIFICATION_TYPES } from '../notification_center/type'; - import OptimizelyUserContext from './'; -import { createLogger } from '../plugins/logger'; import { createNotificationCenter } from '../notification_center'; import Optimizely from '../optimizely'; import errorHandler from '../plugins/error_handler'; @@ -31,7 +27,7 @@ import { OptimizelyDecideOption } from '../shared_types'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; -import * as logger from '../plugins/logger'; + import { USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, @@ -46,16 +42,22 @@ const getMockEventDispatcher = () => { return dispatcher; } +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}); + const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { - const createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); + const createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); const mockConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(datafileObj), }); const eventDispatcher = getMockEventDispatcher(); const eventProcessor = getForwardingEventProcessor(eventDispatcher); - const notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - const optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, @@ -65,12 +67,10 @@ const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { isValidInstance: true, eventBatchSize: 1, defaultDecideOptions: defaultDecideOptions || [], - notificationCenter, }); - sinon.stub(notificationCenter, 'sendNotifications'); - return { optlyInstance, eventProcessor, eventDispatcher, notificationCenter, createdLogger } + return { optlyInstance, eventProcessor, eventDispatcher, createdLogger } } describe('lib/optimizely_user_context', function() { @@ -337,25 +337,17 @@ describe('lib/optimizely_user_context', function() { logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); - var stubLogHandler; - let optlyInstance, notificationCenter, eventDispatcher; - beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); + let optlyInstance, eventDispatcher; - ({ optlyInstance, notificationCenter, createdLogger, eventDispatcher} = getOptlyInstance({ + beforeEach(function() { + ({ optlyInstance, createdLogger, eventDispatcher} = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), })); }); afterEach(function() { - logging.resetLogger(); eventDispatcher.dispatchEvent.reset(); - notificationCenter.sendNotifications.restore(); }); it('should return true when client is not ready', function() { @@ -422,7 +414,7 @@ describe('lib/optimizely_user_context', function() { }); afterEach(function() { - optlyInstance.decisionService.logger.log.restore(); + // optlyInstance.decisionService.logger.log.restore(); eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); @@ -500,10 +492,13 @@ describe('lib/optimizely_user_context', function() { }); it('should return forced decision object when forced decision is set for a flag and dispatch an event', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), }); + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; var variationKey = '3324490562'; @@ -579,9 +574,13 @@ describe('lib/optimizely_user_context', function() { }); it('should return forced decision object when forced decision is set for an experiment rule and dispatch an event', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), }); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var attributes = { country: 'US' }; var user = optlyInstance.createUserContext(userId, attributes); var featureKey = 'feature_1'; @@ -664,9 +663,13 @@ describe('lib/optimizely_user_context', function() { }); it('should return forced decision object when forced decision is set for a delivery rule and dispatch an event', function() { - const { optlyInstance, notificationCenter, eventDispatcher } = getOptlyInstance({ + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), }); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; var variationKey = '3324490633'; @@ -935,18 +938,6 @@ describe('lib/optimizely_user_context', function() { }); describe('#removeForcedDecision', function() { - var stubLogHandler; - beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); - }); - afterEach(function() { - logging.resetLogger(); - }); - it('should return true when client is not ready and the forced decision has been removed successfully', function() { fakeOptimizely = { isValidInstance: sinon.stub().returns(false), @@ -1010,18 +1001,6 @@ describe('lib/optimizely_user_context', function() { }); describe('#removeAllForcedDecisions', function() { - var stubLogHandler; - beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); - }); - afterEach(function() { - logging.resetLogger(); - }); - it('should return true when client is not ready', function() { fakeOptimizely = { isValidInstance: sinon.stub().returns(false), diff --git a/lib/plugins/logger/index.react_native.tests.js b/lib/plugins/logger/index.react_native.tests.js index 7ea19e98a..ad18ddad4 100644 --- a/lib/plugins/logger/index.react_native.tests.js +++ b/lib/plugins/logger/index.react_native.tests.js @@ -1,82 +1,82 @@ -/** - * Copyright 2019-2020 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { assert } from 'chai'; +// /** +// * Copyright 2019-2020 Optimizely +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// import sinon from 'sinon'; +// import { assert } from 'chai'; -import { createLogger } from './index.react_native'; -import { LOG_LEVEL } from '../../utils/enums'; +// import { createLogger } from './index.react_native'; +// import { LOG_LEVEL } from '../../utils/enums'; -describe('lib/plugins/logger/react_native', function() { - describe('APIs', function() { - var defaultLogger; - describe('createLogger', function() { - it('should return an instance of the default logger', function() { - defaultLogger = createLogger(); - assert.isObject(defaultLogger); - }); - }); +// describe('lib/plugins/logger/react_native', function() { +// describe('APIs', function() { +// var defaultLogger; +// describe('createLogger', function() { +// it('should return an instance of the default logger', function() { +// defaultLogger = createLogger(); +// assert.isObject(defaultLogger); +// }); +// }); - describe('log', function() { - beforeEach(function() { - defaultLogger = createLogger(); +// describe('log', function() { +// beforeEach(function() { +// defaultLogger = createLogger(); - sinon.stub(console, 'log'); - sinon.stub(console, 'info'); - sinon.stub(console, 'warn'); - sinon.stub(console, 'error'); - }); +// sinon.stub(console, 'log'); +// sinon.stub(console, 'info'); +// sinon.stub(console, 'warn'); +// sinon.stub(console, 'error'); +// }); - afterEach(function() { - console.log.restore(); - console.info.restore(); - console.warn.restore(); - console.error.restore(); - }); +// afterEach(function() { +// console.log.restore(); +// console.info.restore(); +// console.warn.restore(); +// console.error.restore(); +// }); - it('should use console.info when log level is info', function() { - defaultLogger.log(LOG_LEVEL.INFO, 'message'); - sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); +// it('should use console.info when log level is info', function() { +// defaultLogger.log(LOG_LEVEL.INFO, 'message'); +// sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); +// sinon.assert.notCalled(console.log); +// sinon.assert.notCalled(console.warn); +// sinon.assert.notCalled(console.error); +// }); - it('should use console.log when log level is debug', function() { - defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); - sinon.assert.calledWithExactly(console.log, sinon.match(/.*DEBUG.*message.*/)); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); +// it('should use console.log when log level is debug', function() { +// defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); +// sinon.assert.calledWithExactly(console.log, sinon.match(/.*DEBUG.*message.*/)); +// sinon.assert.notCalled(console.info); +// sinon.assert.notCalled(console.warn); +// sinon.assert.notCalled(console.error); +// }); - it('should use console.warn when log level is warn', function() { - defaultLogger.log(LOG_LEVEL.WARNING, 'message'); - sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARNING.*message.*/)); - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.error); - }); +// it('should use console.warn when log level is warn', function() { +// defaultLogger.log(LOG_LEVEL.WARNING, 'message'); +// sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARNING.*message.*/)); +// sinon.assert.notCalled(console.log); +// sinon.assert.notCalled(console.info); +// sinon.assert.notCalled(console.error); +// }); - it('should use console.warn when log level is error', function() { - defaultLogger.log(LOG_LEVEL.ERROR, 'message'); - sinon.assert.calledWithExactly(console.warn, sinon.match(/.*ERROR.*message.*/)); - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.error); - }); - }); - }); -}); +// it('should use console.warn when log level is error', function() { +// defaultLogger.log(LOG_LEVEL.ERROR, 'message'); +// sinon.assert.calledWithExactly(console.warn, sinon.match(/.*ERROR.*message.*/)); +// sinon.assert.notCalled(console.log); +// sinon.assert.notCalled(console.info); +// sinon.assert.notCalled(console.error); +// }); +// }); +// }); +// }); diff --git a/lib/plugins/logger/index.react_native.ts b/lib/plugins/logger/index.react_native.ts index 5d5ee8ae7..816944a15 100644 --- a/lib/plugins/logger/index.react_native.ts +++ b/lib/plugins/logger/index.react_native.ts @@ -1,60 +1,60 @@ -/** - * Copyright 2019-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LogLevel } from '../../modules/logging'; -import { sprintf } from '../../utils/fns'; -import { NoOpLogger } from './index'; +// /** +// * Copyright 2019-2022, Optimizely +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// import { LogLevel } from '../../modules/logging'; +// import { sprintf } from '../../utils/fns'; +// import { NoOpLogger } from './index'; -function getLogLevelName(level: number): string { - switch (level) { - case LogLevel.INFO: - return 'INFO'; - case LogLevel.ERROR: - return 'ERROR'; - case LogLevel.WARNING: - return 'WARNING'; - case LogLevel.DEBUG: - return 'DEBUG'; - default: - return 'NOTSET'; - } -} +// function getLogLevelName(level: number): string { +// switch (level) { +// case LogLevel.INFO: +// return 'INFO'; +// case LogLevel.ERROR: +// return 'ERROR'; +// case LogLevel.WARNING: +// return 'WARNING'; +// case LogLevel.DEBUG: +// return 'DEBUG'; +// default: +// return 'NOTSET'; +// } +// } -class ReactNativeLogger { - log(level: number, message: string): void { - const formattedMessage = sprintf('[OPTIMIZELY] - %s %s %s', getLogLevelName(level), new Date().toISOString(), message); - switch (level) { - case LogLevel.INFO: - console.info(formattedMessage); - break; - case LogLevel.ERROR: - case LogLevel.WARNING: - console.warn(formattedMessage); - break; - case LogLevel.DEBUG: - case LogLevel.NOTSET: - console.log(formattedMessage); - break; - } - } -} +// class ReactNativeLogger { +// log(level: number, message: string): void { +// const formattedMessage = sprintf('[OPTIMIZELY] - %s %s %s', getLogLevelName(level), new Date().toISOString(), message); +// switch (level) { +// case LogLevel.INFO: +// console.info(formattedMessage); +// break; +// case LogLevel.ERROR: +// case LogLevel.WARNING: +// console.warn(formattedMessage); +// break; +// case LogLevel.DEBUG: +// case LogLevel.NOTSET: +// console.log(formattedMessage); +// break; +// } +// } +// } -export function createLogger(): ReactNativeLogger { - return new ReactNativeLogger(); -} +// export function createLogger(): ReactNativeLogger { +// return new ReactNativeLogger(); +// } -export function createNoOpLogger(): NoOpLogger { - return new NoOpLogger(); -} +// export function createNoOpLogger(): NoOpLogger { +// return new NoOpLogger(); +// } diff --git a/lib/plugins/logger/index.tests.js b/lib/plugins/logger/index.tests.js index 0e3eaac56..cf153a2f0 100644 --- a/lib/plugins/logger/index.tests.js +++ b/lib/plugins/logger/index.tests.js @@ -1,112 +1,112 @@ -/** - * Copyright 2016, 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert, expect } from 'chai'; -import sinon from 'sinon'; - -import { createLogger } from './'; -import { LOG_LEVEL } from '../../utils/enums';; - -describe('lib/plugins/logger', function() { - describe('APIs', function() { - var defaultLogger; - describe('createLogger', function() { - it('should return an instance of the default logger', function() { - defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); - assert.isObject(defaultLogger); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); - }); - }); - - describe('log', function() { - beforeEach(function() { - defaultLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - - sinon.stub(console, 'log'); - sinon.stub(console, 'info'); - sinon.stub(console, 'warn'); - sinon.stub(console, 'error'); - }); - - afterEach(function() { - console.log.restore(); - console.info.restore(); - console.warn.restore(); - console.error.restore(); - }); - - it('should log a message at the threshold log level', function() { - defaultLogger.log(LOG_LEVEL.INFO, 'message'); - - sinon.assert.notCalled(console.log); - sinon.assert.calledOnce(console.info); - sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); - - it('should log a message if its log level is higher than the threshold log level', function() { - defaultLogger.log(LOG_LEVEL.WARNING, 'message'); - - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.calledOnce(console.warn); - sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARN.*message.*/)); - sinon.assert.notCalled(console.error); - }); - - it('should not log a message if its log level is lower than the threshold log level', function() { - defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); - - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); - }); - - describe('setLogLevel', function() { - beforeEach(function() { - defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); - }); - - it('should set the log level to the specified log level', function() { - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); - - defaultLogger.setLogLevel(LOG_LEVEL.DEBUG); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.DEBUG); - - defaultLogger.setLogLevel(LOG_LEVEL.INFO); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.INFO); - }); - - it('should set the log level to the ERROR when log level is not specified', function() { - defaultLogger.setLogLevel(); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - }); - - it('should set the log level to the ERROR when log level is not valid', function() { - defaultLogger.setLogLevel(-123); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - - defaultLogger.setLogLevel(undefined); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - - defaultLogger.setLogLevel('abc'); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - }); - }); - }); -}); +// /** +// * Copyright 2016, 2020, Optimizely +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// import { assert, expect } from 'chai'; +// import sinon from 'sinon'; + +// import { createLogger } from './'; +// import { LOG_LEVEL } from '../../utils/enums';; + +// describe('lib/plugins/logger', function() { +// describe('APIs', function() { +// var defaultLogger; +// describe('createLogger', function() { +// it('should return an instance of the default logger', function() { +// defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); +// assert.isObject(defaultLogger); +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); +// }); +// }); + +// describe('log', function() { +// beforeEach(function() { +// defaultLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + +// sinon.stub(console, 'log'); +// sinon.stub(console, 'info'); +// sinon.stub(console, 'warn'); +// sinon.stub(console, 'error'); +// }); + +// afterEach(function() { +// console.log.restore(); +// console.info.restore(); +// console.warn.restore(); +// console.error.restore(); +// }); + +// it('should log a message at the threshold log level', function() { +// defaultLogger.log(LOG_LEVEL.INFO, 'message'); + +// sinon.assert.notCalled(console.log); +// sinon.assert.calledOnce(console.info); +// sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); +// sinon.assert.notCalled(console.warn); +// sinon.assert.notCalled(console.error); +// }); + +// it('should log a message if its log level is higher than the threshold log level', function() { +// defaultLogger.log(LOG_LEVEL.WARNING, 'message'); + +// sinon.assert.notCalled(console.log); +// sinon.assert.notCalled(console.info); +// sinon.assert.calledOnce(console.warn); +// sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARN.*message.*/)); +// sinon.assert.notCalled(console.error); +// }); + +// it('should not log a message if its log level is lower than the threshold log level', function() { +// defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); + +// sinon.assert.notCalled(console.log); +// sinon.assert.notCalled(console.info); +// sinon.assert.notCalled(console.warn); +// sinon.assert.notCalled(console.error); +// }); +// }); + +// describe('setLogLevel', function() { +// beforeEach(function() { +// defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); +// }); + +// it('should set the log level to the specified log level', function() { +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); + +// defaultLogger.setLogLevel(LOG_LEVEL.DEBUG); +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.DEBUG); + +// defaultLogger.setLogLevel(LOG_LEVEL.INFO); +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.INFO); +// }); + +// it('should set the log level to the ERROR when log level is not specified', function() { +// defaultLogger.setLogLevel(); +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); +// }); + +// it('should set the log level to the ERROR when log level is not valid', function() { +// defaultLogger.setLogLevel(-123); +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); + +// defaultLogger.setLogLevel(undefined); +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); + +// defaultLogger.setLogLevel('abc'); +// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); +// }); +// }); +// }); +// }); diff --git a/lib/plugins/logger/index.ts b/lib/plugins/logger/index.ts deleted file mode 100644 index a9a24e7bb..000000000 --- a/lib/plugins/logger/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2016-2017, 2020-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { ConsoleLogHandler, LogLevel } from '../../modules/logging'; - -type ConsoleLogHandlerConfig = { - logLevel?: LogLevel | string; - logToConsole?: boolean; - prefix?: string; -} - -export class NoOpLogger { - log(): void { } -} - -export function createLogger(opts?: ConsoleLogHandlerConfig): ConsoleLogHandler { - return new ConsoleLogHandler(opts); -} - -export function createNoOpLogger(): NoOpLogger { - return new NoOpLogger(); -} diff --git a/lib/project_config/config_manager_factory.spec.ts b/lib/project_config/config_manager_factory.spec.ts index e30cbf33e..1ad4dc689 100644 --- a/lib/project_config/config_manager_factory.spec.ts +++ b/lib/project_config/config_manager_factory.spec.ts @@ -38,7 +38,7 @@ import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater import { getPollingConfigManager } from './config_manager_factory'; import { DEFAULT_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; import { getMockSyncCache } from '../tests/mock/mock_cache'; -import { LogLevel } from '../modules/logging'; +import { LogLevel } from '../logging/logger'; describe('getPollingConfigManager', () => { const MockProjectConfigManagerImpl = vi.mocked(ProjectConfigManagerImpl); @@ -86,7 +86,7 @@ describe('getPollingConfigManager', () => { getPollingConfigManager(config); const startupLogs = MockPollingDatafileManager.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE, params: [], }])); diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 8cde539fa..141952148 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -24,7 +24,7 @@ import { DEFAULT_UPDATE_INTERVAL } from './constant'; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; import { StartupLog } from "../service"; import { MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; -import { LogLevel } from "../modules/logging"; +import { LogLevel } from '../logging/logger' export type StaticConfigManagerConfig = { datafile: string, @@ -62,7 +62,7 @@ export const getPollingConfigManager = ( if (updateInterval < MIN_UPDATE_INTERVAL) { startupLogs.push({ - level: LogLevel.WARNING, + level: LogLevel.Warn, message: UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE, params: [], }); diff --git a/lib/project_config/datafile_manager.ts b/lib/project_config/datafile_manager.ts index 3f38ea53c..c1b58704b 100644 --- a/lib/project_config/datafile_manager.ts +++ b/lib/project_config/datafile_manager.ts @@ -18,7 +18,7 @@ import { Cache } from '../utils/cache/cache'; import { RequestHandler } from '../utils/http_request_handler/http'; import { Fn, Consumer } from '../utils/type'; import { Repeater } from '../utils/repeater/repeater'; -import { LoggerFacade } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger'; export interface DatafileManager extends Service { get(): string | undefined; diff --git a/lib/project_config/optimizely_config.ts b/lib/project_config/optimizely_config.ts index 52eeb016c..b01255c43 100644 --- a/lib/project_config/optimizely_config.ts +++ b/lib/project_config/optimizely_config.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerFacade, getLogger } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger' import { ProjectConfig } from '../project_config/project_config'; import { DEFAULT_OPERATOR_TYPES } from '../core/condition_tree_evaluator'; import { diff --git a/lib/project_config/polling_datafile_manager.spec.ts b/lib/project_config/polling_datafile_manager.spec.ts index 642061d96..c8f68a1cc 100644 --- a/lib/project_config/polling_datafile_manager.spec.ts +++ b/lib/project_config/polling_datafile_manager.spec.ts @@ -23,7 +23,7 @@ import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE, MIN_UPDATE_IN import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { ServiceState, StartupLog } from '../service'; import { getMockSyncCache, getMockAsyncCache } from '../tests/mock/mock_cache'; -import { LogLevel } from '../modules/logging'; +import { LogLevel } from '../logging/logger'; describe('PollingDatafileManager', () => { it('should log polling interval below MIN_UPDATE_INTERVAL', () => { @@ -33,12 +33,12 @@ describe('PollingDatafileManager', () => { const startupLogs: StartupLog[] = [ { - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'warn message', params: [1, 2] }, { - level: LogLevel.ERROR, + level: LogLevel.Error, message: 'error message', params: [3, 4] }, @@ -53,8 +53,8 @@ describe('PollingDatafileManager', () => { }); manager.start(); - expect(logger.log).toHaveBeenNthCalledWith(1, LogLevel.WARNING, 'warn message', 1, 2); - expect(logger.log).toHaveBeenNthCalledWith(2, LogLevel.ERROR, 'error message', 3, 4); + expect(logger.warn).toHaveBeenNthCalledWith(1, 'warn message', 1, 2); + expect(logger.error).toHaveBeenNthCalledWith(1, 'error message', 3, 4); }); diff --git a/lib/project_config/project_config.tests.js b/lib/project_config/project_config.tests.js index 6bfc34d67..e776ebf72 100644 --- a/lib/project_config/project_config.tests.js +++ b/lib/project_config/project_config.tests.js @@ -17,18 +17,31 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { forEach, cloneDeep } from 'lodash'; import { sprintf } from '../utils/fns'; -import { getLogger } from '../modules/logging'; - import fns from '../utils/fns'; import projectConfig from './project_config'; import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; -import * as loggerPlugin from '../plugins/logger'; import testDatafile from '../tests/test_data'; import configValidator from '../utils/config_validator'; -import { INVALID_EXPERIMENT_ID, INVALID_EXPERIMENT_KEY } from '../error_messages'; +import { + INVALID_EXPERIMENT_ID, + INVALID_EXPERIMENT_KEY, + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, + UNRECOGNIZED_ATTRIBUTE, + VARIABLE_KEY_NOT_IN_DATAFILE, + FEATURE_NOT_IN_DATAFILE, + UNABLE_TO_CAST_VALUE +} from '../error_messages'; + +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); -var logger = getLogger(); +var logger = createLogger(); describe('lib/core/project_config', function() { describe('createProjectConfig method', function() { @@ -280,15 +293,15 @@ describe('lib/core/project_config', function() { describe('projectConfig helper methods', function() { var testData = cloneDeep(testDatafile.getTestProjectConfig()); var configObj; - var createdLogger = loggerPlugin.createLogger({ logLevel: LOG_LEVEL.INFO }); + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); beforeEach(function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'warn'); }); afterEach(function() { - createdLogger.log.restore(); + createdLogger.warn.restore(); }); it('should retrieve experiment ID for valid experiment key in getExperimentId', function() { @@ -324,10 +337,8 @@ describe('lib/core/project_config', function() { it('should return null for invalid attribute key in getAttributeId', function() { assert.isNull(projectConfig.getAttributeId(configObj, 'invalidAttributeKey', createdLogger)); - assert.strictEqual( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unrecognized attribute invalidAttributeKey provided. Pruning before sending event to Optimizely.' - ); + + assert.deepEqual(createdLogger.warn.lastCall.args, [UNRECOGNIZED_ATTRIBUTE, 'invalidAttributeKey']); }); it('should return null for invalid attribute key in getAttributeId', function() { @@ -337,10 +348,8 @@ describe('lib/core/project_config', function() { key: '$opt_some_reserved_attribute', }; assert.strictEqual(projectConfig.getAttributeId(configObj, '$opt_some_reserved_attribute', createdLogger), '42'); - assert.strictEqual( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'Attribute $opt_some_reserved_attribute unexpectedly has reserved prefix $opt_; using attribute ID instead of reserved attribute name.' - ); + + assert.deepEqual(createdLogger.warn.lastCall.args, [UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, '$opt_some_reserved_attribute', '$opt_']); }); it('should retrieve event ID for valid event key in getEventId', function() { @@ -431,14 +440,20 @@ describe('lib/core/project_config', function() { }); describe('feature management', function() { - var featureManagementLogger = loggerPlugin.createLogger({ logLevel: LOG_LEVEL.INFO }); + var featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); - sinon.stub(featureManagementLogger, 'log'); + sinon.stub(featureManagementLogger, 'warn'); + sinon.stub(featureManagementLogger, 'error'); + sinon.stub(featureManagementLogger, 'info'); + sinon.stub(featureManagementLogger, 'debug'); }); afterEach(function() { - featureManagementLogger.log.restore(); + featureManagementLogger.warn.restore(); + featureManagementLogger.error.restore(); + featureManagementLogger.info.restore(); + featureManagementLogger.debug.restore(); }); describe('getVariableForFeature', function() { @@ -459,11 +474,9 @@ describe('lib/core/project_config', function() { var variableKey = 'notARealVariable____'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); assert.strictEqual(result, null); - sinon.assert.calledOnce(featureManagementLogger.log); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "notARealVariable____" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); + sinon.assert.calledOnce(featureManagementLogger.error); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [VARIABLE_KEY_NOT_IN_DATAFILE, 'notARealVariable____', 'test_feature_for_experiment']); }); it('should return null for an invalid feature key', function() { @@ -471,11 +484,9 @@ describe('lib/core/project_config', function() { var variableKey = 'num_buttons'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); assert.strictEqual(result, null); - sinon.assert.calledOnce(featureManagementLogger.log); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key notARealFeature_____ is not in datafile.' - ); + sinon.assert.calledOnce(featureManagementLogger.error); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____']); }); it('should return null for an invalid variable key and an invalid feature key', function() { @@ -483,11 +494,9 @@ describe('lib/core/project_config', function() { var variableKey = 'notARealVariable____'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); assert.strictEqual(result, null); - sinon.assert.calledOnce(featureManagementLogger.log); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key notARealFeature_____ is not in datafile.' - ); + sinon.assert.calledOnce(featureManagementLogger.error); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____']); }); }); @@ -629,10 +638,8 @@ describe('lib/core/project_config', function() { featureManagementLogger ); assert.strictEqual(result, null); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value notabool to type boolean, returning null.' - ); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [UNABLE_TO_CAST_VALUE, 'notabool', 'boolean']); }); it('returns null and logs an error for an invalid integer', function() { @@ -642,10 +649,8 @@ describe('lib/core/project_config', function() { featureManagementLogger ); assert.strictEqual(result, null); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value notanint to type integer, returning null.' - ); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [UNABLE_TO_CAST_VALUE, 'notanint', 'integer']); }); it('returns null and logs an error for an invalid double', function() { @@ -655,10 +660,8 @@ describe('lib/core/project_config', function() { featureManagementLogger ); assert.strictEqual(result, null); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value notadouble to type double, returning null.' - ); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [UNABLE_TO_CAST_VALUE, 'notadouble', 'double']); }); }); }); diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 781470ab2..3671928ac 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -18,7 +18,8 @@ import { find, objectEntries, objectValues, sprintf, keyBy } from '../utils/fns' import { LOG_LEVEL, FEATURE_VARIABLE_TYPES } from '../utils/enums'; import configValidator from '../utils/config_validator'; -import { LogHandler } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger'; + import { Audience, Experiment, @@ -43,6 +44,7 @@ import { INVALID_EXPERIMENT_KEY, MISSING_INTEGRATION_KEY, UNABLE_TO_CAST_VALUE, + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, UNRECOGNIZED_ATTRIBUTE, VARIABLE_KEY_NOT_IN_DATAFILE, VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, @@ -54,7 +56,7 @@ interface TryCreatingProjectConfigConfig { // eslint-disable-next-line @typescript-eslint/ban-types datafile: string | object; jsonSchemaValidator?: Transformer<unknown, boolean>; - logger?: LogHandler; + logger?: LoggerFacade; } interface Event { @@ -389,15 +391,14 @@ export const getLayerId = function(projectConfig: ProjectConfig, experimentId: s export const getAttributeId = function( projectConfig: ProjectConfig, attributeKey: string, - logger: LogHandler + logger?: LoggerFacade ): string | null { const attribute = projectConfig.attributeKeyMap[attributeKey]; const hasReservedPrefix = attributeKey.indexOf(RESERVED_ATTRIBUTE_PREFIX) === 0; if (attribute) { if (hasReservedPrefix) { - logger.log( - LOG_LEVEL.WARNING, - 'Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.', + logger?.warn( + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, attributeKey, RESERVED_ATTRIBUTE_PREFIX ); @@ -407,7 +408,7 @@ export const getAttributeId = function( return attributeKey; } - logger.log(LOG_LEVEL.DEBUG, UNRECOGNIZED_ATTRIBUTE, MODULE_NAME, attributeKey); + logger?.warn(UNRECOGNIZED_ATTRIBUTE, attributeKey); return null; }; @@ -575,7 +576,7 @@ export const getTrafficAllocation = function(projectConfig: ProjectConfig, exper export const getExperimentFromId = function( projectConfig: ProjectConfig, experimentId: string, - logger: LogHandler + logger?: LoggerFacade ): Experiment | null { if (projectConfig.experimentIdMap.hasOwnProperty(experimentId)) { const experiment = projectConfig.experimentIdMap[experimentId]; @@ -584,7 +585,7 @@ export const getExperimentFromId = function( } } - logger.log(LOG_LEVEL.ERROR, INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId); + logger?.error(INVALID_EXPERIMENT_ID, experimentId); return null; }; @@ -624,7 +625,7 @@ export const getFlagVariationByKey = function( export const getFeatureFromKey = function( projectConfig: ProjectConfig, featureKey: string, - logger: LogHandler + logger?: LoggerFacade ): FeatureFlag | null { if (projectConfig.featureKeyMap.hasOwnProperty(featureKey)) { const feature = projectConfig.featureKeyMap[featureKey]; @@ -633,7 +634,7 @@ export const getFeatureFromKey = function( } } - logger.log(LOG_LEVEL.ERROR, FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); + logger?.error(FEATURE_NOT_IN_DATAFILE, featureKey); return null; }; @@ -652,17 +653,17 @@ export const getVariableForFeature = function( projectConfig: ProjectConfig, featureKey: string, variableKey: string, - logger: LogHandler + logger?: LoggerFacade ): FeatureVariable | null { const feature = projectConfig.featureKeyMap[featureKey]; if (!feature) { - logger.log(LOG_LEVEL.ERROR, FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); + logger?.error(FEATURE_NOT_IN_DATAFILE, featureKey); return null; } const variable = feature.variableKeyMap[variableKey]; if (!variable) { - logger.log(LOG_LEVEL.ERROR, VARIABLE_KEY_NOT_IN_DATAFILE, MODULE_NAME, variableKey, featureKey); + logger?.error(VARIABLE_KEY_NOT_IN_DATAFILE, variableKey, featureKey); return null; } @@ -685,14 +686,14 @@ export const getVariableValueForVariation = function( projectConfig: ProjectConfig, variable: FeatureVariable, variation: Variation, - logger: LogHandler + logger?: LoggerFacade ): string | null { if (!variable || !variation) { return null; } if (!projectConfig.variationVariableUsageMap.hasOwnProperty(variation.id)) { - logger.log(LOG_LEVEL.ERROR, VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, MODULE_NAME, variation.id); + logger?.error(VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, variation.id); return null; } @@ -721,14 +722,14 @@ export const getVariableValueForVariation = function( export const getTypeCastValue = function( variableValue: string, variableType: VariableType, - logger: LogHandler + logger?: LoggerFacade ): FeatureVariableValue { let castValue : FeatureVariableValue; switch (variableType) { case FEATURE_VARIABLE_TYPES.BOOLEAN: if (variableValue !== 'true' && variableValue !== 'false') { - logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } else { castValue = variableValue === 'true'; @@ -738,7 +739,7 @@ export const getTypeCastValue = function( case FEATURE_VARIABLE_TYPES.INTEGER: castValue = parseInt(variableValue, 10); if (isNaN(castValue)) { - logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } break; @@ -746,7 +747,7 @@ export const getTypeCastValue = function( case FEATURE_VARIABLE_TYPES.DOUBLE: castValue = parseFloat(variableValue); if (isNaN(castValue)) { - logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } break; @@ -755,7 +756,7 @@ export const getTypeCastValue = function( try { castValue = JSON.parse(variableValue); } catch (e) { - logger.log(LOG_LEVEL.ERROR, UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType); + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } break; @@ -833,9 +834,9 @@ export const tryCreatingProjectConfig = function( if (config.jsonSchemaValidator) { config.jsonSchemaValidator(newDatafileObj); - config.logger?.log(LOG_LEVEL.INFO, VALID_DATAFILE, MODULE_NAME); + config.logger?.info(VALID_DATAFILE); } else { - config.logger?.log(LOG_LEVEL.INFO, SKIPPING_JSON_VALIDATION, MODULE_NAME); + config.logger?.info(SKIPPING_JSON_VALIDATION, MODULE_NAME); } const createProjectConfigArgs = [newDatafileObj]; diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index a0ebbffdb..4a851347e 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerFacade } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger'; import { createOptimizelyConfig } from './optimizely_config'; import { OptimizelyConfig } from '../shared_types'; import { DatafileManager } from './datafile_manager'; diff --git a/lib/service.spec.ts b/lib/service.spec.ts index 12df4feff..0b9d7c754 100644 --- a/lib/service.spec.ts +++ b/lib/service.spec.ts @@ -16,7 +16,7 @@ import { it, expect } from 'vitest'; import { BaseService, ServiceState, StartupLog } from './service'; -import { LogLevel } from './modules/logging'; +import { LogLevel } from './logging/logger'; import { getMockLogger } from './tests/mock/mock_logger'; class TestService extends BaseService { constructor(startUpLogs?: StartupLog[]) { @@ -69,12 +69,12 @@ it('should return correct state when getState() is called', () => { it('should log startupLogs on start', () => { const startUpLogs: StartupLog[] = [ { - level: LogLevel.WARNING, + level: LogLevel.Warn, message: 'warn message', params: [1, 2] }, { - level: LogLevel.ERROR, + level: LogLevel.Error, message: 'error message', params: [3, 4] }, @@ -85,9 +85,10 @@ it('should log startupLogs on start', () => { service.setLogger(logger); service.start(); - expect(logger.log).toHaveBeenCalledTimes(2); - expect(logger.log).toHaveBeenNthCalledWith(1, LogLevel.WARNING, 'warn message', 1, 2); - expect(logger.log).toHaveBeenNthCalledWith(2, LogLevel.ERROR, 'error message', 3, 4); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('warn message', 1, 2); + expect(logger.error).toHaveBeenCalledWith('error message', 3, 4); }); it('should return an appropraite promise when onRunning() is called', () => { diff --git a/lib/service.ts b/lib/service.ts index 2d0877bee..a43bcadc0 100644 --- a/lib/service.ts +++ b/lib/service.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LoggerFacade, LogLevel } from "./modules/logging"; +import { LoggerFacade, LogLevel } from './logging/logger' import { resolvablePromise, ResolvablePromise } from "./utils/promise/resolvablePromise"; @@ -81,9 +81,15 @@ export abstract class BaseService implements Service { } protected printStartupLogs(): void { - this.startupLogs.forEach(({ level, message, params }) => { - this.logger?.log(level, message, ...params); - }); + if (!this.logger) { + return; + } + + for (const { level, message, params } of this.startupLogs) { + const methodName = LogLevel[level].toLowerCase(); + const method = this.logger[methodName as keyof LoggerFacade]; + method.call(this.logger, message, ...params); + } } onRunning(): Promise<void> { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 299dc9332..b38f096cc 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -19,7 +19,9 @@ * These shared type definitions include ones that will be referenced by external consumers via export_types.ts. */ -import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; +// import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; +import { LoggerFacade, LogLevel } from './logging/logger'; +import { ErrorHandler } from './error/error_handler'; import { NotificationCenter, DefaultNotificationCenter } from './notification_center'; @@ -37,6 +39,7 @@ import { ProjectConfigManager } from './project_config/project_config_manager'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor/event_processor'; import { VuidManager } from './vuid/vuid_manager'; +import { ErrorNotifier } from './error/error_notifier'; export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; export { EventProcessor } from './event_processor/event_processor'; @@ -52,7 +55,7 @@ export interface BucketerParams { experimentIdMap: { [id: string]: Experiment }; groupIdMap: { [key: string]: Group }; variationIdMap: { [id: string]: Variation }; - logger: LogHandler; + logger?: LoggerFacade; bucketingId: string; } @@ -253,18 +256,16 @@ export interface OptimizelyOptions { // eslint-disable-next-line @typescript-eslint/ban-types datafile?: string | object; datafileManager?: DatafileManager; - errorHandler: ErrorHandler; + errorNotifier?: ErrorNotifier; eventProcessor?: EventProcessor; - isValidInstance: boolean; jsonSchemaValidator?: { validate(jsonObject: unknown): boolean; }; - logger: LoggerFacade; + logger?: LoggerFacade; sdkKey?: string; userProfileService?: UserProfileService | null; defaultDecideOptions?: OptimizelyDecideOption[]; odpManager?: OdpManager; - notificationCenter: DefaultNotificationCenter; vuidManager?: VuidManager disposable?: boolean; } @@ -374,10 +375,8 @@ export interface Config { jsonSchemaValidator?: { validate(jsonObject: unknown): boolean; }; - // level of logging i.e debug, info, error, warning etc - logLevel?: LogLevel | string; // LogHandler object for logging - logger?: LogHandler; + logger?: LoggerFacade; // user profile that contains user information userProfileService?: UserProfileService; // dafault options for decide API diff --git a/lib/tests/mock/mock_datafile_manager.ts b/lib/tests/mock/mock_datafile_manager.ts index f2aa450b9..1c9d66b38 100644 --- a/lib/tests/mock/mock_datafile_manager.ts +++ b/lib/tests/mock/mock_datafile_manager.ts @@ -18,7 +18,7 @@ import { Consumer } from '../../utils/type'; import { DatafileManager } from '../../project_config/datafile_manager'; import { EventEmitter } from '../../utils/event_emitter/event_emitter'; import { BaseService } from '../../service'; -import { LoggerFacade } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; type MockConfig = { datafile?: string | object; diff --git a/lib/tests/mock/mock_logger.ts b/lib/tests/mock/mock_logger.ts index 7af7d26e8..f9ee207e4 100644 --- a/lib/tests/mock/mock_logger.ts +++ b/lib/tests/mock/mock_logger.ts @@ -15,14 +15,14 @@ */ import { vi } from 'vitest'; -import { LoggerFacade } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; export const getMockLogger = () : LoggerFacade => { return { info: vi.fn(), - log: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), + child: vi.fn().mockImplementation(() => getMockLogger()), }; }; diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index 12e0ca0d9..f3c2eadfd 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -53,7 +53,7 @@ export const validate = function(config: unknown): boolean { if (eventDispatcher && typeof (eventDispatcher as ObjectWithUnknownProperties)['dispatchEvent'] !== 'function') { throw new Error(sprintf(INVALID_EVENT_DISPATCHER, MODULE_NAME)); } - if (logger && typeof (logger as ObjectWithUnknownProperties)['log'] !== 'function') { + if (logger && typeof (logger as ObjectWithUnknownProperties)['info'] !== 'function') { throw new Error(sprintf(INVALID_LOGGER, MODULE_NAME)); } return true; diff --git a/lib/utils/event_tag_utils/index.tests.js b/lib/utils/event_tag_utils/index.tests.js index 5f7fc2bcc..f1e6e8834 100644 --- a/lib/utils/event_tag_utils/index.tests.js +++ b/lib/utils/event_tag_utils/index.tests.js @@ -18,15 +18,23 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import * as eventTagUtils from './'; +import { FAILED_TO_PARSE_REVENUE, PARSED_REVENUE_VALUE, PARSED_NUMERIC_VALUE, FAILED_TO_PARSE_VALUE } from '../../log_messages'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/utils/event_tag_utils', function() { var mockLogger; beforeEach(function() { - mockLogger = { - log: sinon.stub(), - }; + mockLogger = createLogger(); + sinon.stub(mockLogger, 'info'); }); describe('APIs', function() { @@ -41,8 +49,9 @@ describe('lib/utils/event_tag_utils', function() { ); assert.strictEqual(parsedRevenueValue, 1337); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Parsed revenue value "1337" from event tags.'); + + assert.strictEqual(mockLogger.info.args[0][0], PARSED_REVENUE_VALUE); + assert.strictEqual(mockLogger.info.args[0][1], 1337); // test out a float parsedRevenueValue = eventTagUtils.getRevenueValue( @@ -67,8 +76,8 @@ describe('lib/utils/event_tag_utils', function() { assert.strictEqual(parsedRevenueValue, null); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Failed to parse revenue value "invalid" from event tags.'); + assert.strictEqual(mockLogger.info.args[0][0], FAILED_TO_PARSE_REVENUE); + assert.strictEqual(mockLogger.info.args[0][1], 'invalid'); }); }); @@ -97,8 +106,9 @@ describe('lib/utils/event_tag_utils', function() { ); assert.strictEqual(parsedEventValue, 1337); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Parsed event value "1337" from event tags.'); + + assert.strictEqual(mockLogger.info.args[0][0], PARSED_NUMERIC_VALUE); + assert.strictEqual(mockLogger.info.args[0][1], 1337); // test out a float parsedEventValue = eventTagUtils.getEventValue( @@ -123,8 +133,8 @@ describe('lib/utils/event_tag_utils', function() { assert.strictEqual(parsedEventValue, null); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Failed to parse event value "invalid" from event tags.'); + assert.strictEqual(mockLogger.info.args[0][0], FAILED_TO_PARSE_VALUE); + assert.strictEqual(mockLogger.info.args[0][1], 'invalid'); }); }); diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index fab537adb..c8fc9835f 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -20,7 +20,7 @@ import { PARSED_REVENUE_VALUE, } from '../../log_messages'; import { EventTags } from '../../event_processor/event_builder/user_event'; -import { LoggerFacade } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { LOG_LEVEL, @@ -40,7 +40,7 @@ const VALUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.VALUE; * @param {LoggerFacade} logger * @return {number|null} */ -export function getRevenueValue(eventTags: EventTags, logger: LoggerFacade): number | null { +export function getRevenueValue(eventTags: EventTags, logger?: LoggerFacade): number | null { const rawValue = eventTags[REVENUE_EVENT_METRIC_NAME]; if (rawValue == null) { // null or undefined event values @@ -50,10 +50,10 @@ export function getRevenueValue(eventTags: EventTags, logger: LoggerFacade): num const parsedRevenueValue = typeof rawValue === 'string' ? parseInt(rawValue) : rawValue; if (isFinite(parsedRevenueValue)) { - logger.log(LOG_LEVEL.INFO, PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); + logger?.info(PARSED_REVENUE_VALUE, parsedRevenueValue); return parsedRevenueValue; } else { // NaN, +/- infinity values - logger.log(LOG_LEVEL.INFO, FAILED_TO_PARSE_REVENUE, MODULE_NAME, rawValue); + logger?.info(FAILED_TO_PARSE_REVENUE, rawValue); return null; } } @@ -64,7 +64,7 @@ export function getRevenueValue(eventTags: EventTags, logger: LoggerFacade): num * @param {LoggerFacade} logger * @return {number|null} */ -export function getEventValue(eventTags: EventTags, logger: LoggerFacade): number | null { +export function getEventValue(eventTags: EventTags, logger?: LoggerFacade): number | null { const rawValue = eventTags[VALUE_EVENT_METRIC_NAME]; if (rawValue == null) { // null or undefined event values @@ -74,10 +74,10 @@ export function getEventValue(eventTags: EventTags, logger: LoggerFacade): numbe const parsedEventValue = typeof rawValue === 'string' ? parseFloat(rawValue) : rawValue; if (isFinite(parsedEventValue)) { - logger.log(LOG_LEVEL.INFO, PARSED_NUMERIC_VALUE, MODULE_NAME, parsedEventValue); + logger?.info(PARSED_NUMERIC_VALUE, parsedEventValue); return parsedEventValue; } else { // NaN, +/- infinity values - logger.log(LOG_LEVEL.INFO, FAILED_TO_PARSE_VALUE, MODULE_NAME, rawValue); + logger?.info(FAILED_TO_PARSE_VALUE, rawValue); return null; } } diff --git a/lib/utils/fns/index.spec.ts b/lib/utils/fns/index.spec.ts index 6f93c6ac6..3d1cd1502 100644 --- a/lib/utils/fns/index.spec.ts +++ b/lib/utils/fns/index.spec.ts @@ -1,24 +1,8 @@ import { describe, it, expect } from 'vitest'; -import { isValidEnum, groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '.' +import { groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '.' describe('utils', () => { - describe('isValidEnum', () => { - enum myEnum { - FOO = 0, - BAR = 1, - } - - it('should return false when not valid', () => { - expect(isValidEnum(myEnum, 2)).toBe(false) - }) - - it('should return true when valid', () => { - expect(isValidEnum(myEnum, 1)).toBe(true) - expect(isValidEnum(myEnum, myEnum.FOO)).toBe(true) - }) - }) - describe('groupBy', () => { it('should group values by some key function', () => { const input = [ diff --git a/lib/utils/fns/index.ts b/lib/utils/fns/index.ts index e7ea3d071..3a427f5dc 100644 --- a/lib/utils/fns/index.ts +++ b/lib/utils/fns/index.ts @@ -48,29 +48,6 @@ export function getTimestamp(): number { return new Date().getTime(); } -/** - * Validates a value is a valid TypeScript enum - * - * @export - * @param {object} enumToCheck - * @param {*} value - * @returns {boolean} - */ -// TODO[OASIS-6649]: Don't use any type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function isValidEnum(enumToCheck: { [key: string]: any }, value: number | string): boolean { - let found = false; - - const keys = Object.keys(enumToCheck); - for (let index = 0; index < keys.length; index++) { - if (value === enumToCheck[keys[index]]) { - found = true; - break; - } - } - return found; -} - export function groupBy<K>(arr: K[], grouperFn: (item: K) => string): Array<K[]> { const grouper: { [key: string]: K[] } = {}; @@ -148,7 +125,6 @@ export default { uuid, isNumber, getTimestamp, - isValidEnum, groupBy, objectValues, objectEntries, diff --git a/lib/utils/http_request_handler/request_handler.browser.spec.ts b/lib/utils/http_request_handler/request_handler.browser.spec.ts index 0bb0d98ed..68a8a5bb7 100644 --- a/lib/utils/http_request_handler/request_handler.browser.spec.ts +++ b/lib/utils/http_request_handler/request_handler.browser.spec.ts @@ -18,7 +18,7 @@ import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; import { BrowserRequestHandler } from './request_handler.browser'; -import { NoOpLogger } from '../../plugins/logger'; +import { getMockLogger } from '../../tests/mock/mock_logger'; describe('BrowserRequestHandler', () => { const host = 'https://endpoint.example.com/api/query'; @@ -34,7 +34,7 @@ describe('BrowserRequestHandler', () => { xhrs = []; mockXHR = fakeXhr.useFakeXMLHttpRequest(); mockXHR.onCreate = (request): number => xhrs.push(request); - browserRequestHandler = new BrowserRequestHandler({ logger: new NoOpLogger() }); + browserRequestHandler = new BrowserRequestHandler({ logger: getMockLogger() }); }); afterEach(() => { @@ -135,7 +135,7 @@ describe('BrowserRequestHandler', () => { const onCreateMock = vi.fn(); mockXHR.onCreate = onCreateMock; - new BrowserRequestHandler({ logger: new NoOpLogger(), timeout }).makeRequest(host, {}, 'get'); + new BrowserRequestHandler({ logger: getMockLogger(), timeout }).makeRequest(host, {}, 'get'); expect(onCreateMock).toBeCalledTimes(1); expect(onCreateMock.mock.calls[0][0].timeout).toBe(timeout); diff --git a/lib/utils/http_request_handler/request_handler.browser.ts b/lib/utils/http_request_handler/request_handler.browser.ts index 26e22425d..88157e6a9 100644 --- a/lib/utils/http_request_handler/request_handler.browser.ts +++ b/lib/utils/http_request_handler/request_handler.browser.ts @@ -15,19 +15,19 @@ */ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; -import { LogHandler, LogLevel } from '../../modules/logging'; +import { LoggerFacade, LogLevel } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; -import { REQUEST_ERROR, REQUEST_TIMEOUT } from '../../exception_messages'; +import { REQUEST_ERROR, REQUEST_TIMEOUT } from '../../error_messages'; import { UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from '../../log_messages'; /** * Handles sending requests and receiving responses over HTTP via XMLHttpRequest */ export class BrowserRequestHandler implements RequestHandler { - private logger?: LogHandler; + private logger?: LoggerFacade; private timeout: number; - public constructor(opt: { logger?: LogHandler, timeout?: number } = {}) { + public constructor(opt: { logger?: LoggerFacade, timeout?: number } = {}) { this.logger = opt.logger; this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS; } @@ -69,7 +69,7 @@ export class BrowserRequestHandler implements RequestHandler { request.timeout = this.timeout; request.ontimeout = (): void => { - this.logger?.log(LogLevel.WARNING, REQUEST_TIMEOUT); + this.logger?.warn(REQUEST_TIMEOUT); }; request.send(data); @@ -124,7 +124,7 @@ export class BrowserRequestHandler implements RequestHandler { } } } catch { - this.logger?.log(LogLevel.WARNING, UNABLE_TO_PARSE_AND_SKIPPED_HEADER, headerLine); + this.logger?.warn(UNABLE_TO_PARSE_AND_SKIPPED_HEADER, headerLine); } }); return headers; diff --git a/lib/utils/http_request_handler/request_handler.node.spec.ts b/lib/utils/http_request_handler/request_handler.node.spec.ts index ef10fbc21..1865df88b 100644 --- a/lib/utils/http_request_handler/request_handler.node.spec.ts +++ b/lib/utils/http_request_handler/request_handler.node.spec.ts @@ -19,7 +19,7 @@ import { describe, beforeEach, afterEach, beforeAll, afterAll, it, vi, expect } import nock from 'nock'; import zlib from 'zlib'; import { NodeRequestHandler } from './request_handler.node'; -import { NoOpLogger } from '../../plugins/logger'; +import { getMockLogger } from '../../tests/mock/mock_logger'; beforeAll(() => { nock.disableNetConnect(); @@ -37,7 +37,7 @@ describe('NodeRequestHandler', () => { let nodeRequestHandler: NodeRequestHandler; beforeEach(() => { - nodeRequestHandler = new NodeRequestHandler({ logger: new NoOpLogger() }); + nodeRequestHandler = new NodeRequestHandler({ logger: getMockLogger() }); }); afterEach(async () => { @@ -218,7 +218,7 @@ describe('NodeRequestHandler', () => { }; scope.on('request', requestListener); - const request = new NodeRequestHandler({ logger: new NoOpLogger(), timeout: 100 }).makeRequest(`${host}${path}`, {}, 'get'); + const request = new NodeRequestHandler({ logger: getMockLogger(), timeout: 100 }).makeRequest(`${host}${path}`, {}, 'get'); vi.advanceTimersByTime(60000); vi.runAllTimers(); // <- explicitly tell vi to run all setTimeout, setInterval diff --git a/lib/utils/http_request_handler/request_handler.node.ts b/lib/utils/http_request_handler/request_handler.node.ts index 0530553b4..6626510e8 100644 --- a/lib/utils/http_request_handler/request_handler.node.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -18,19 +18,19 @@ import https from 'https'; import url from 'url'; import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import decompressResponse from 'decompress-response'; -import { LogHandler } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; import { sprintf } from '../fns'; -import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from '../../exception_messages'; +import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from '../../error_messages'; /** * Handles sending requests and receiving responses over HTTP via NodeJS http module */ export class NodeRequestHandler implements RequestHandler { - private readonly logger?: LogHandler; + private readonly logger?: LoggerFacade; private readonly timeout: number; - constructor(opt: { logger?: LogHandler; timeout?: number } = {}) { + constructor(opt: { logger?: LoggerFacade; timeout?: number } = {}) { this.logger = opt.logger; this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS; } diff --git a/lib/utils/semantic_version/index.ts b/lib/utils/semantic_version/index.ts index cdc479bf0..ecd7bb804 100644 --- a/lib/utils/semantic_version/index.ts +++ b/lib/utils/semantic_version/index.ts @@ -14,11 +14,10 @@ * limitations under the License. */ import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; -import { getLogger } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { VERSION_TYPE } from '../enums'; const MODULE_NAME = 'SEMANTIC VERSION'; -const logger = getLogger(); /** * Evaluate if provided string is number only @@ -88,13 +87,13 @@ function hasWhiteSpaces(version: string): boolean { * @return {boolean} The array of version split into smaller parts i.e major, minor, patch etc * null if given version is in invalid format */ -function splitVersion(version: string): string[] | null { +function splitVersion(version: string, logger?: LoggerFacade): string[] | null { let targetPrefix = version; let targetSuffix = ''; // check that version shouldn't have white space if (hasWhiteSpaces(version)) { - logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } //check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release @@ -114,18 +113,18 @@ function splitVersion(version: string): string[] | null { const dotCount = targetPrefix.split('.').length - 1; if (dotCount > 2) { - logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } const targetVersionParts = targetPrefix.split('.'); if (targetVersionParts.length != dotCount + 1) { - logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } for (const part of targetVersionParts) { if (!isNumber(part)) { - logger.warn(UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } } @@ -146,9 +145,9 @@ function splitVersion(version: string): string[] | null { * -1 if user version is less than condition version * null if invalid user or condition version is provided */ -export function compareVersion(conditionsVersion: string, userProvidedVersion: string): number | null { - const userVersionParts = splitVersion(userProvidedVersion); - const conditionsVersionParts = splitVersion(conditionsVersion); +export function compareVersion(conditionsVersion: string, userProvidedVersion: string, logger?: LoggerFacade): number | null { + const userVersionParts = splitVersion(userProvidedVersion, logger); + const conditionsVersionParts = splitVersion(conditionsVersion, logger); if (!userVersionParts || !conditionsVersionParts) { return null; diff --git a/lib/vuid/vuid_manager.ts b/lib/vuid/vuid_manager.ts index 8de680609..32ca67103 100644 --- a/lib/vuid/vuid_manager.ts +++ b/lib/vuid/vuid_manager.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerFacade } from '../modules/logging'; +import { LoggerFacade } from '../logging/logger'; import { Cache } from '../utils/cache/cache'; import { AsyncProducer, Maybe } from '../utils/type'; import { isVuid, makeVuid } from './vuid'; diff --git a/message_generator.ts b/message_generator.ts index d4b03fb04..fae725a1c 100644 --- a/message_generator.ts +++ b/message_generator.ts @@ -18,7 +18,7 @@ const generate = async () => { let genOut = ''; Object.keys(exports).forEach((key, i) => { - const msg = exports[key]; + if (key === 'messages') return; genOut += `export const ${key} = '${i}';\n`; messages.push(exports[key]) }); From 02d7c7758a4caf365e6d0bce7da41a9ce609ba26 Mon Sep 17 00:00:00 2001 From: esrakartalOpt <102107327+esrakartalOpt@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:30:32 -0600 Subject: [PATCH 111/200] Bump happy-dom from 14.12.3 to 16.6.0 (#984) Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 14.12.3 to 16.6.0. - [Release notes](https://github.com/capricorn86/happy-dom/releases) - [Commits](https://github.com/capricorn86/happy-dom/compare/v14.12.3...v16.6.0) --- updated-dependencies: - dependency-name: happy-dom dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 13 +++++++------ package.json | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index e37a742e1..07043aa32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", - "happy-dom": "^14.12.3", + "happy-dom": "^16.6.0", "jiti": "^2.4.1", "json-loader": "^0.5.4", "karma": "^6.4.0", @@ -8098,6 +8098,8 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.12" }, @@ -9245,17 +9247,16 @@ "dev": true }, "node_modules/happy-dom": { - "version": "14.12.3", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.12.3.tgz", - "integrity": "sha512-vsYlEs3E9gLwA1Hp+w3qzu+RUDFf4VTT8cyKqVICoZ2k7WM++Qyd2LwzyTi5bqMJFiIC/vNpTDYuxdreENRK/g==", + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.6.0.tgz", + "integrity": "sha512-Zz5S9sog8a3p8XYZbO+eI1QMOAvCNnIoyrH8A8MLX+X2mJrzADTy+kdETmc4q+uD9AGAvQYGn96qBAn2RAciKw==", "dev": true, "dependencies": { - "entities": "^4.5.0", "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/has": { diff --git a/package.json b/package.json index b4f553c4b..367d40125 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", - "happy-dom": "^14.12.3", + "happy-dom": "^16.6.0", "jiti": "^2.4.1", "json-loader": "^0.5.4", "karma": "^6.4.0", From 36d7ef781f08954fbedf150fb35b1eeb963c010c Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 21 Jan 2025 15:48:46 +0600 Subject: [PATCH 112/200] [FSSDK-11035] add logger factory and tests (#985) --- lib/error/error_notifier.spec.ts | 50 ++ lib/error/error_notifier.ts | 16 +- lib/error/error_notifier_factory.ts | 34 ++ lib/error/error_reporter.spec.ts | 60 +++ lib/error/error_reporter.ts | 15 + lib/error/optimizly_error.ts | 19 +- lib/index.browser.ts | 16 +- lib/index.lite.ts | 65 +-- lib/index.node.tests.js | 4 +- lib/index.node.ts | 15 +- lib/index.react_native.spec.ts | 8 +- lib/index.react_native.ts | 15 +- lib/logging/logger.spec.ts | 775 ++++++++++++++-------------- lib/logging/logger.ts | 33 +- lib/logging/logger_factory.spec.ts | 66 +++ lib/logging/logger_factory.ts | 112 +++- lib/service.ts | 4 +- lib/shared_types.ts | 13 +- 18 files changed, 822 insertions(+), 498 deletions(-) create mode 100644 lib/error/error_notifier.spec.ts create mode 100644 lib/error/error_notifier_factory.ts create mode 100644 lib/error/error_reporter.spec.ts create mode 100644 lib/logging/logger_factory.spec.ts diff --git a/lib/error/error_notifier.spec.ts b/lib/error/error_notifier.spec.ts new file mode 100644 index 000000000..7c2b19d89 --- /dev/null +++ b/lib/error/error_notifier.spec.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; + +import { DefaultErrorNotifier } from './error_notifier'; +import { OptimizelyError } from './optimizly_error'; + +const mockMessageResolver = (prefix = '') => { + return { + resolve: vi.fn().mockImplementation((message) => `${prefix} ${message}`), + }; +} + +describe('DefaultErrorNotifier', () => { + it('should call the error handler with the error if the error is not an OptimizelyError', () => { + const errorHandler = { handleError: vi.fn() }; + const messageResolver = mockMessageResolver(); + const errorNotifier = new DefaultErrorNotifier(errorHandler, messageResolver); + + const error = new Error('error'); + errorNotifier.notify(error); + + expect(errorHandler.handleError).toHaveBeenCalledWith(error); + }); + + it('should resolve the message of an OptimizelyError before calling the error handler', () => { + const errorHandler = { handleError: vi.fn() }; + const messageResolver = mockMessageResolver('err'); + const errorNotifier = new DefaultErrorNotifier(errorHandler, messageResolver); + + const error = new OptimizelyError('test %s', 'one'); + errorNotifier.notify(error); + + expect(errorHandler.handleError).toHaveBeenCalledWith(error); + expect(error.message).toBe('err test one'); + }); +}); diff --git a/lib/error/error_notifier.ts b/lib/error/error_notifier.ts index 6a00eaf1e..174c163e2 100644 --- a/lib/error/error_notifier.ts +++ b/lib/error/error_notifier.ts @@ -1,5 +1,19 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { MessageResolver } from "../message/message_resolver"; -import { sprintf } from "../utils/fns"; import { ErrorHandler } from "./error_handler"; import { OptimizelyError } from "./optimizly_error"; diff --git a/lib/error/error_notifier_factory.ts b/lib/error/error_notifier_factory.ts new file mode 100644 index 000000000..970723591 --- /dev/null +++ b/lib/error/error_notifier_factory.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { errorResolver } from "../message/message_resolver"; +import { ErrorHandler } from "./error_handler"; +import { DefaultErrorNotifier } from "./error_notifier"; + +const errorNotifierSymbol = Symbol(); + +export type OpaqueErrorNotifier = { + [errorNotifierSymbol]: unknown; +}; + +export const createErrorNotifier = (errorHandler: ErrorHandler): OpaqueErrorNotifier => { + return { + [errorNotifierSymbol]: new DefaultErrorNotifier(errorHandler, errorResolver), + } +} + +export const extractErrorNotifier = (errorNotifier: OpaqueErrorNotifier): DefaultErrorNotifier => { + return errorNotifier[errorNotifierSymbol] as DefaultErrorNotifier; +} diff --git a/lib/error/error_reporter.spec.ts b/lib/error/error_reporter.spec.ts new file mode 100644 index 000000000..abdd932d0 --- /dev/null +++ b/lib/error/error_reporter.spec.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; + +import { ErrorReporter } from './error_reporter'; + +import { OptimizelyError } from './optimizly_error'; + +const mockMessageResolver = (prefix = '') => { + return { + resolve: vi.fn().mockImplementation((message) => `${prefix} ${message}`), + }; +} + +describe('ErrorReporter', () => { + it('should call the logger and errorNotifier with the first argument if it is an Error object', () => { + const logger = { error: vi.fn() }; + const errorNotifier = { notify: vi.fn() }; + const errorReporter = new ErrorReporter(logger as any, errorNotifier as any); + + const error = new Error('error'); + errorReporter.report(error); + + expect(logger.error).toHaveBeenCalledWith(error); + expect(errorNotifier.notify).toHaveBeenCalledWith(error); + }); + + it('should create an OptimizelyError and call the logger and errorNotifier with it if the first argument is a string', () => { + const logger = { error: vi.fn() }; + const errorNotifier = { notify: vi.fn() }; + const errorReporter = new ErrorReporter(logger as any, errorNotifier as any); + + errorReporter.report('message', 1, 2); + + expect(logger.error).toHaveBeenCalled(); + const loggedError = logger.error.mock.calls[0][0]; + expect(loggedError).toBeInstanceOf(OptimizelyError); + expect(loggedError.baseMessage).toBe('message'); + expect(loggedError.params).toEqual([1, 2]); + + expect(errorNotifier.notify).toHaveBeenCalled(); + const notifiedError = errorNotifier.notify.mock.calls[0][0]; + expect(notifiedError).toBeInstanceOf(OptimizelyError); + expect(notifiedError.baseMessage).toBe('message'); + expect(notifiedError.params).toEqual([1, 2]); + }); +}); diff --git a/lib/error/error_reporter.ts b/lib/error/error_reporter.ts index 9a9aa69d2..130527928 100644 --- a/lib/error/error_reporter.ts +++ b/lib/error/error_reporter.ts @@ -1,3 +1,18 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { LoggerFacade } from "../logging/logger"; import { ErrorNotifier } from "./error_notifier"; import { OptimizelyError } from "./optimizly_error"; diff --git a/lib/error/optimizly_error.ts b/lib/error/optimizly_error.ts index 4c60a237b..76e8f7734 100644 --- a/lib/error/optimizly_error.ts +++ b/lib/error/optimizly_error.ts @@ -1,9 +1,24 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { MessageResolver } from "../message/message_resolver"; import { sprintf } from "../utils/fns"; export class OptimizelyError extends Error { - private baseMessage: string; - private params: any[]; + baseMessage: string; + params: any[]; private resolved = false; constructor(baseMessage: string, ...params: any[]) { super(); diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 4834a09c8..054c584d8 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -31,6 +31,10 @@ import { createBatchEventProcessor, createForwardingEventProcessor } from './eve import { createVuidManager } from './vuid/vuid_manager_factory.browser'; import { createOdpManager } from './odp/odp_manager_factory.browser'; import { ODP_DISABLED, UNABLE_TO_ATTACH_UNLOAD } from './log_messages'; +import { extractLogger, createLogger } from './logging/logger_factory'; +import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; +import { LoggerFacade } from './logging/logger'; +import { Maybe } from './utils/type'; const MODULE_NAME = 'INDEX_BROWSER'; @@ -47,15 +51,21 @@ let hasRetriedEvents = false; * null on error */ const createInstance = function(config: Config): Client | null { + let logger: Maybe<LoggerFacade>; + try { configValidator.validate(config); const { clientEngine, clientVersion } = config; + logger = config.logger ? extractLogger(config.logger) : undefined; + const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; const optimizelyOptions: OptimizelyOptions = { ...config, clientEngine: clientEngine || enums.JAVASCRIPT_CLIENT_ENGINE, clientVersion: clientVersion || enums.CLIENT_VERSION, + logger, + errorNotifier, }; const optimizely = new Optimizely(optimizelyOptions); @@ -73,13 +83,13 @@ const createInstance = function(config: Config): Client | null { } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - config.logger?.error(UNABLE_TO_ATTACH_UNLOAD, e.message); + logger?.error(UNABLE_TO_ATTACH_UNLOAD, e.message); } return optimizely; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - config.logger?.error(e); + logger?.error(e); return null; } }; @@ -103,6 +113,8 @@ export { createBatchEventProcessor, createOdpManager, createVuidManager, + createLogger, + createErrorNotifier, }; export * from './common_exports'; diff --git a/lib/index.lite.ts b/lib/index.lite.ts index 0e00e33d4..ace83107d 100644 --- a/lib/index.lite.ts +++ b/lib/index.lite.ts @@ -1,64 +1 @@ -/** - * Copyright 2021-2022, 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; -import * as enums from './utils/enums'; -import Optimizely from './optimizely'; -import { createNotificationCenter } from './notification_center'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import * as commonExports from './common_exports'; - -/** - * Creates an instance of the Optimizely class - * @param {ConfigLite} config - * @return {Client|null} the Optimizely client object - * null on error - */ - const createInstance = function(config: Config): Client | null { - try { - configValidator.validate(config); - - const optimizelyOptions = { - clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, - ...config, - }; - - const optimizely = new Optimizely(optimizelyOptions); - return optimizely; - } catch (e: any) { - config.logger?.error(e); - return null; - } -}; - -export { - defaultErrorHandler as errorHandler, - enums, - createInstance, - OptimizelyDecideOption, -}; - -export * from './common_exports'; - -export default { - ...commonExports, - errorHandler: defaultErrorHandler, - enums, - createInstance, - OptimizelyDecideOption, -}; - -export * from './export_types' +const msg = 'not used'; \ No newline at end of file diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 891edc137..6d2bba594 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -69,7 +69,7 @@ describe('optimizelyFactory', function() { // sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); // }); - it('should not throw if the provided config is not valid and log an error if no logger is provided', function() { + it('should not throw if the provided config is not valid', function() { configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ @@ -77,7 +77,7 @@ describe('optimizelyFactory', function() { logger: fakeLogger, }); }); - sinon.assert.calledOnce(fakeLogger.error); + // sinon.assert.calledOnce(fakeLogger.error); }); // it('should create an instance of optimizely', function() { diff --git a/lib/index.node.ts b/lib/index.node.ts index 995510baa..ba31fcbee 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -29,6 +29,11 @@ import { createVuidManager } from './vuid/vuid_manager_factory.node'; import { createOdpManager } from './odp/odp_manager_factory.node'; import { ODP_DISABLED } from './log_messages'; import { create } from 'domain'; +import { extractLogger, createLogger } from './logging/logger_factory'; +import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; +import { Maybe } from './utils/type'; +import { LoggerFacade } from './logging/logger'; +import { ErrorNotifier } from './error/error_notifier'; const DEFAULT_EVENT_BATCH_SIZE = 10; const DEFAULT_EVENT_FLUSH_INTERVAL = 30000; // Unit is ms, default is 30s @@ -41,21 +46,27 @@ const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; * null on error */ const createInstance = function(config: Config): Client | null { + let logger: Maybe<LoggerFacade>; + try { configValidator.validate(config); const { clientEngine, clientVersion } = config; + logger = config.logger ? extractLogger(config.logger) : undefined; + const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; const optimizelyOptions = { ...config, clientEngine: clientEngine || enums.NODE_CLIENT_ENGINE, clientVersion: clientVersion || enums.CLIENT_VERSION, + logger, + errorNotifier, }; return new Optimizely(optimizelyOptions); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - config.logger?.error(e); + logger?.error(e); return null; } }; @@ -74,6 +85,8 @@ export { createBatchEventProcessor, createOdpManager, createVuidManager, + createLogger, + createErrorNotifier, }; export * from './common_exports'; diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index c61e9cf37..8132b9e76 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -82,10 +82,10 @@ describe('javascript-sdk/react-native', () => { it('should create an instance of optimizely', () => { const optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, + // errorHandler: fakeErrorHandler, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - logger: mockLogger, + // logger: mockLogger, }); expect(optlyInstance).toBeInstanceOf(Optimizely); @@ -97,10 +97,10 @@ describe('javascript-sdk/react-native', () => { it('should set the React Native JS client engine and javascript SDK version', () => { const optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: fakeErrorHandler, + // errorHandler: fakeErrorHandler, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - logger: mockLogger, + // logger: mockLogger, }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index a7bc5853f..bfbea0aca 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -29,6 +29,10 @@ import { createVuidManager } from './vuid/vuid_manager_factory.react_native'; import 'fast-text-encoding'; import 'react-native-get-random-values'; +import { Maybe } from './utils/type'; +import { LoggerFacade } from './logging/logger'; +import { extractLogger, createLogger } from './logging/logger_factory'; +import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; const DEFAULT_EVENT_BATCH_SIZE = 10; const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s @@ -41,15 +45,22 @@ const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; * null on error */ const createInstance = function(config: Config): Client | null { + let logger: Maybe<LoggerFacade>; + try { configValidator.validate(config); const { clientEngine, clientVersion } = config; + logger = config.logger ? extractLogger(config.logger) : undefined; + const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; + const optimizelyOptions = { ...config, clientEngine: clientEngine || enums.REACT_NATIVE_JS_CLIENT_ENGINE, clientVersion: clientVersion || enums.CLIENT_VERSION, + logger, + errorNotifier, }; // If client engine is react, convert it to react native. @@ -60,7 +71,7 @@ const createInstance = function(config: Config): Client | null { return new Optimizely(optimizelyOptions); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { - config.logger?.error(e); + logger?.error(e); return null; } }; @@ -79,6 +90,8 @@ export { createBatchEventProcessor, createOdpManager, createVuidManager, + createLogger, + createErrorNotifier, }; export * from './common_exports'; diff --git a/lib/logging/logger.spec.ts b/lib/logging/logger.spec.ts index e0a8d6ac6..59edd3f96 100644 --- a/lib/logging/logger.spec.ts +++ b/lib/logging/logger.spec.ts @@ -1,389 +1,386 @@ -import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; - -it.skip('pass', () => {}); -// import { -// LogLevel, -// LogHandler, -// LoggerFacade, -// } from './models' - -// import { -// setLogHandler, -// setLogLevel, -// getLogger, -// ConsoleLogHandler, -// resetLogger, -// getLogLevel, -// } from './logger' - -// import { resetErrorHandler } from './errorHandler' -// import { ErrorHandler, setErrorHandler } from './errorHandler' - -// describe('logger', () => { -// afterEach(() => { -// resetLogger() -// resetErrorHandler() -// }) - -// describe('OptimizelyLogger', () => { -// let stubLogger: LogHandler -// let logger: LoggerFacade -// let stubErrorHandler: ErrorHandler - -// beforeEach(() => { -// stubLogger = { -// log: vi.fn(), -// } -// stubErrorHandler = { -// handleError: vi.fn(), -// } -// setLogLevel(LogLevel.DEBUG) -// setLogHandler(stubLogger) -// setErrorHandler(stubErrorHandler) -// logger = getLogger() -// }) - -// describe('setLogLevel', () => { -// it('should coerce "debug"', () => { -// setLogLevel('debug') -// expect(getLogLevel()).toBe(LogLevel.DEBUG) -// }) - -// it('should coerce "deBug"', () => { -// setLogLevel('deBug') -// expect(getLogLevel()).toBe(LogLevel.DEBUG) -// }) - -// it('should coerce "INFO"', () => { -// setLogLevel('INFO') -// expect(getLogLevel()).toBe(LogLevel.INFO) -// }) - -// it('should coerce "WARN"', () => { -// setLogLevel('WARN') -// expect(getLogLevel()).toBe(LogLevel.WARNING) -// }) - -// it('should coerce "warning"', () => { -// setLogLevel('warning') -// expect(getLogLevel()).toBe(LogLevel.WARNING) -// }) - -// it('should coerce "ERROR"', () => { -// setLogLevel('WARN') -// expect(getLogLevel()).toBe(LogLevel.WARNING) -// }) - -// it('should default to error if invalid', () => { -// setLogLevel('invalid') -// expect(getLogLevel()).toBe(LogLevel.ERROR) -// }) -// }) - -// describe('getLogger(name)', () => { -// it('should prepend the name in the log messages', () => { -// const myLogger = getLogger('doit') -// myLogger.info('test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'doit: test') -// }) -// }) - -// describe('logger.log(level, msg)', () => { -// it('should work with a string logLevel', () => { -// setLogLevel(LogLevel.INFO) -// logger.log('info', 'test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') -// }) - -// it('should call the loggerBackend when the message logLevel is equal to the configured logLevel threshold', () => { -// setLogLevel(LogLevel.INFO) -// logger.log(LogLevel.INFO, 'test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') -// }) - -// it('should call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { -// setLogLevel(LogLevel.INFO) -// logger.log(LogLevel.WARNING, 'test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') -// }) - -// it('should not call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { -// setLogLevel(LogLevel.INFO) -// logger.log(LogLevel.DEBUG, 'test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(0) -// }) - -// it('should not throw if loggerBackend is not supplied', () => { -// setLogLevel(LogLevel.INFO) -// logger.log(LogLevel.ERROR, 'test') -// }) -// }) - -// describe('logger.info', () => { -// it('should handle info(message)', () => { -// logger.info('test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') -// }) -// it('should handle info(message, ...splat)', () => { -// logger.info('test: %s %s', 'hey', 'jude') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey jude') -// }) - -// it('should handle info(message, ...splat, error)', () => { -// const error = new Error('hey') -// logger.info('test: %s', 'hey', error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) - -// it('should handle info(error)', () => { -// const error = new Error('hey') -// logger.info(error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) -// }) - -// describe('logger.debug', () => { -// it('should handle debug(message)', () => { -// logger.debug('test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test') -// }) - -// it('should handle debug(message, ...splat)', () => { -// logger.debug('test: %s', 'hey') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') -// }) - -// it('should handle debug(message, ...splat, error)', () => { -// const error = new Error('hey') -// logger.debug('test: %s', 'hey', error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) - -// it('should handle debug(error)', () => { -// const error = new Error('hey') -// logger.debug(error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) -// }) - -// describe('logger.warn', () => { -// it('should handle warn(message)', () => { -// logger.warn('test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') -// }) - -// it('should handle warn(message, ...splat)', () => { -// logger.warn('test: %s', 'hey') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') -// }) - -// it('should handle warn(message, ...splat, error)', () => { -// const error = new Error('hey') -// logger.warn('test: %s', 'hey', error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) - -// it('should handle info(error)', () => { -// const error = new Error('hey') -// logger.warn(error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) -// }) - -// describe('logger.error', () => { -// it('should handle error(message)', () => { -// logger.error('test') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test') -// }) - -// it('should handle error(message, ...splat)', () => { -// logger.error('test: %s', 'hey') - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') -// }) - -// it('should handle error(message, ...splat, error)', () => { -// const error = new Error('hey') -// logger.error('test: %s', 'hey', error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) - -// it('should handle error(error)', () => { -// const error = new Error('hey') -// logger.error(error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) - -// it('should work with an insufficient amount of splat args error(msg, ...splat, message)', () => { -// const error = new Error('hey') -// logger.error('hey %s', error) - -// expect(stubLogger.log).toHaveBeenCalledTimes(1) -// expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey undefined') -// expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) -// }) -// }) - -// describe('using ConsoleLoggerHandler', () => { -// beforeEach(() => { -// vi.spyOn(console, 'info').mockImplementation(() => {}) -// }) - -// afterEach(() => { -// vi.resetAllMocks() -// }) - -// it('should work with BasicLogger', () => { -// const logger = new ConsoleLogHandler() -// const TIME = '12:00' -// setLogHandler(logger) -// setLogLevel(LogLevel.INFO) -// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - -// logger.log(LogLevel.INFO, 'hey') - -// expect(console.info).toBeCalledTimes(1) -// expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 hey') -// }) - -// it('should set logLevel to ERROR when setLogLevel is called with invalid value', () => { -// const logger = new ConsoleLogHandler() -// logger.setLogLevel('invalid' as any) - -// expect(logger.logLevel).toEqual(LogLevel.ERROR) -// }) - -// it('should set logLevel to ERROR when setLogLevel is called with no value', () => { -// const logger = new ConsoleLogHandler() -// // eslint-disable-next-line @typescript-eslint/ban-ts-comment -// // @ts-ignore -// logger.setLogLevel() - -// expect(logger.logLevel).toEqual(LogLevel.ERROR) -// }) -// }) -// }) - -// describe('ConsoleLogger', function() { -// beforeEach(() => { -// vi.spyOn(console, 'info') -// vi.spyOn(console, 'log') -// vi.spyOn(console, 'warn') -// vi.spyOn(console, 'error') -// }) - -// afterEach(() => { -// vi.resetAllMocks() -// }) - -// it('should log to console.info for LogLevel.INFO', () => { -// const logger = new ConsoleLogHandler({ -// logLevel: LogLevel.DEBUG, -// }) -// const TIME = '12:00' -// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - -// logger.log(LogLevel.INFO, 'test') - -// expect(console.info).toBeCalledTimes(1) -// expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 test') -// }) - -// it('should log to console.log for LogLevel.DEBUG', () => { -// const logger = new ConsoleLogHandler({ -// logLevel: LogLevel.DEBUG, -// }) -// const TIME = '12:00' -// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - -// logger.log(LogLevel.DEBUG, 'debug') - -// expect(console.log).toBeCalledTimes(1) -// expect(console.log).toBeCalledWith('[OPTIMIZELY] - DEBUG 12:00 debug') -// }) - -// it('should log to console.warn for LogLevel.WARNING', () => { -// const logger = new ConsoleLogHandler({ -// logLevel: LogLevel.DEBUG, -// }) -// const TIME = '12:00' -// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - -// logger.log(LogLevel.WARNING, 'warning') - -// expect(console.warn).toBeCalledTimes(1) -// expect(console.warn).toBeCalledWith('[OPTIMIZELY] - WARN 12:00 warning') -// }) - -// it('should log to console.error for LogLevel.ERROR', () => { -// const logger = new ConsoleLogHandler({ -// logLevel: LogLevel.DEBUG, -// }) -// const TIME = '12:00' -// vi.spyOn(logger, 'getTime').mockImplementation(() => TIME) - -// logger.log(LogLevel.ERROR, 'error') - -// expect(console.error).toBeCalledTimes(1) -// expect(console.error).toBeCalledWith('[OPTIMIZELY] - ERROR 12:00 error') -// }) - -// it('should not log if the configured logLevel is higher', () => { -// const logger = new ConsoleLogHandler({ -// logLevel: LogLevel.INFO, -// }) - -// logger.log(LogLevel.DEBUG, 'debug') - -// expect(console.log).toBeCalledTimes(0) -// }) -// }) -// }) +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, beforeEach, afterEach, it, expect, vi, afterAll } from 'vitest'; + +import { ConsoleLogHandler, LogLevel, OptimizelyLogger } from './logger'; +import { OptimizelyError } from '../error/optimizly_error'; + +describe('ConsoleLogHandler', () => { + const logSpy = vi.spyOn(console, 'log'); + const debugSpy = vi.spyOn(console, 'debug'); + const infoSpy = vi.spyOn(console, 'info'); + const warnSpy = vi.spyOn(console, 'warn'); + const errorSpy = vi.spyOn(console, 'error'); + + beforeEach(() => { + logSpy.mockClear(); + debugSpy.mockClear(); + infoSpy.mockClear(); + warnSpy.mockClear(); + vi.useFakeTimers().setSystemTime(0); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + afterAll(() => { + logSpy.mockRestore(); + debugSpy.mockRestore(); + infoSpy.mockRestore(); + warnSpy.mockRestore(); + errorSpy.mockRestore(); + + vi.useRealTimers(); + }); + + it('should call console.info for LogLevel.Info', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Info, 'test'); + + expect(infoSpy).toHaveBeenCalledTimes(1); + }); + + it('should call console.debug for LogLevel.Debug', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Debug, 'test'); + + expect(debugSpy).toHaveBeenCalledTimes(1); + }); + + + it('should call console.warn for LogLevel.Warn', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Warn, 'test'); + + expect(warnSpy).toHaveBeenCalledTimes(1); + }); + + it('should call console.error for LogLevel.Error', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Error, 'test'); + + expect(errorSpy).toHaveBeenCalledTimes(1); + }); + + it('should format the log message', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Info, 'info message'); + logger.log(LogLevel.Debug, 'debug message'); + logger.log(LogLevel.Warn, 'warn message'); + logger.log(LogLevel.Error, 'error message'); + + expect(infoSpy).toHaveBeenCalledWith('[OPTIMIZELY] - INFO 1970-01-01T00:00:00.000Z info message'); + expect(debugSpy).toHaveBeenCalledWith('[OPTIMIZELY] - DEBUG 1970-01-01T00:00:00.000Z debug message'); + expect(warnSpy).toHaveBeenCalledWith('[OPTIMIZELY] - WARN 1970-01-01T00:00:00.000Z warn message'); + expect(errorSpy).toHaveBeenCalledWith('[OPTIMIZELY] - ERROR 1970-01-01T00:00:00.000Z error message'); + }); + + it('should use the prefix if provided', () => { + const logger = new ConsoleLogHandler('PREFIX'); + logger.log(LogLevel.Info, 'info message'); + logger.log(LogLevel.Debug, 'debug message'); + logger.log(LogLevel.Warn, 'warn message'); + logger.log(LogLevel.Error, 'error message'); + + expect(infoSpy).toHaveBeenCalledWith('PREFIX - INFO 1970-01-01T00:00:00.000Z info message'); + expect(debugSpy).toHaveBeenCalledWith('PREFIX - DEBUG 1970-01-01T00:00:00.000Z debug message'); + expect(warnSpy).toHaveBeenCalledWith('PREFIX - WARN 1970-01-01T00:00:00.000Z warn message'); + expect(errorSpy).toHaveBeenCalledWith('PREFIX - ERROR 1970-01-01T00:00:00.000Z error message'); + }); +}); + + +const mockMessageResolver = (prefix = '') => { + return { + resolve: vi.fn().mockImplementation((message) => `${prefix} ${message}`), + }; +} + +const mockLogHandler = () => { + return { + log: vi.fn(), + }; +} + +describe('OptimizelyLogger', () => { + it('should only log error when level is set to error', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + errorMsgResolver: messageResolver, + level: LogLevel.Error, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + }); + + it('should only log warn and error when level is set to warn', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + errorMsgResolver: messageResolver, + level: LogLevel.Warn, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log.mock.calls[1][0]).toBe(LogLevel.Warn); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + }); + + it('should only log info, warn and error when level is set to info', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: messageResolver, + errorMsgResolver: messageResolver, + level: LogLevel.Info, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log.mock.calls[1][0]).toBe(LogLevel.Warn); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(3); + expect(logHandler.log.mock.calls[2][0]).toBe(LogLevel.Info); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(3); + }); + + it('should log all levels when level is set to debug', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: messageResolver, + errorMsgResolver: messageResolver, + level: LogLevel.Debug, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log.mock.calls[1][0]).toBe(LogLevel.Warn); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(3); + expect(logHandler.log.mock.calls[2][0]).toBe(LogLevel.Info); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log.mock.calls[3][0]).toBe(LogLevel.Debug); + }); + + it('should skip logging debug/info levels if not infoMessageResolver is available', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + errorMsgResolver: messageResolver, + level: LogLevel.Debug, + }); + + logger.info('test'); + logger.debug('test'); + expect(logHandler.log).not.toHaveBeenCalled(); + }); + + it('should resolve debug/info messages using the infoMessageResolver', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.debug('msg one'); + logger.info('msg two'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Debug, 'info msg one'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Info, 'info msg two'); + }); + + it('should resolve warn/error messages using the infoMessageResolver', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.warn('msg one'); + logger.error('msg two'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Warn, 'err msg one'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Error, 'err msg two'); + }); + + it('should use the provided name as message prefix', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.warn('msg one'); + logger.error('msg two'); + logger.debug('msg three'); + logger.info('msg four'); + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Warn, 'EventManager: err msg one'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Error, 'EventManager: err msg two'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Debug, 'EventManager: info msg three'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Info, 'EventManager: info msg four'); + }); + + it('should format the message with the give parameters', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.warn('msg %s, %s', 'one', 1); + logger.error('msg %s', 'two'); + logger.debug('msg three', 9999); + logger.info('msg four%s%s', '!', '!'); + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Warn, 'EventManager: err msg one, 1'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Error, 'EventManager: err msg two'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Debug, 'EventManager: info msg three'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Info, 'EventManager: info msg four!!'); + }); + + it('should log the message of the error object and ignore other arguments if first argument is an error object \ + other that OptimizelyError', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + logger.debug(new Error('msg debug %s'), 'a'); + logger.info(new Error('msg info %s'), 'b'); + logger.warn(new Error('msg warn %s'), 'c'); + logger.error(new Error('msg error %s'), 'd'); + + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Debug, 'EventManager: msg debug %s'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Info, 'EventManager: msg info %s'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Warn, 'EventManager: msg warn %s'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Error, 'EventManager: msg error %s'); + }); + + it('should resolve and log the message of an OptimizelyError using error resolver and ignore other arguments', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + const err = new OptimizelyError('msg %s %s', 1, 2); + logger.debug(err, 'a'); + logger.info(err, 'a'); + logger.warn(err, 'a'); + logger.error(err, 'a'); + + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Debug, 'EventManager: err msg 1 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Info, 'EventManager: err msg 1 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Warn, 'EventManager: err msg 1 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Error, 'EventManager: err msg 1 2'); + }); + + it('should return a new logger with the new name but same level, handler and resolvers when child() is called', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Info, + }); + + const childLogger = logger.child('ChildLogger'); + childLogger.debug('msg one %s', 1); + childLogger.info('msg two %s', 2); + childLogger.warn('msg three %s', 3); + childLogger.error('msg four %s', 4); + + expect(logHandler.log).toHaveBeenCalledTimes(3); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Info, 'ChildLogger: info msg two 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Warn, 'ChildLogger: err msg three 3'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Error, 'ChildLogger: err msg four 4'); + }); +}); diff --git a/lib/logging/logger.ts b/lib/logging/logger.ts index 408a06710..568bd2cac 100644 --- a/lib/logging/logger.ts +++ b/lib/logging/logger.ts @@ -1,7 +1,7 @@ /** - * Copyright 2019, 2024, Optimizely + * Copyright 2019, 2024, 2025, Optimizely * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -24,6 +24,20 @@ export enum LogLevel { Error, } +export const LogLevelToUpper: Record<LogLevel, string> = { + [LogLevel.Debug]: 'DEBUG', + [LogLevel.Info]: 'INFO', + [LogLevel.Warn]: 'WARN', + [LogLevel.Error]: 'ERROR', +}; + +export const LogLevelToLower: Record<LogLevel, string> = { + [LogLevel.Debug]: 'debug', + [LogLevel.Info]: 'info', + [LogLevel.Warn]: 'warn', + [LogLevel.Error]: 'error', +}; + export interface LoggerFacade { info(message: string | Error, ...args: any[]): void; debug(message: string | Error, ...args: any[]): void; @@ -44,7 +58,7 @@ export class ConsoleLogHandler implements LogHandler { } log(level: LogLevel, message: string) : void { - const log = `${this.prefix} - ${level} ${this.getTime()} ${message}` + const log = `${this.prefix} - ${LogLevelToUpper[level]} ${this.getTime()} ${message}` this.consoleLog(level, log) } @@ -53,9 +67,10 @@ export class ConsoleLogHandler implements LogHandler { } private consoleLog(logLevel: LogLevel, log: string) : void { - const methodName = LogLevel[logLevel].toLowerCase() + const methodName: string = LogLevelToLower[logLevel]; + const method: any = console[methodName as keyof Console] || console.log; - method.bind(console)(log); + method.call(console, log); } } @@ -90,7 +105,7 @@ export class OptimizelyLogger implements LoggerFacade { infoMsgResolver: this.infoResolver, errorMsgResolver: this.errorResolver, level: this.level, - name: `${this.name}.${name}`, + name, }); } @@ -111,11 +126,13 @@ export class OptimizelyLogger implements LoggerFacade { } private handleLog(level: LogLevel, message: string, args: any[]) { - const log = `${this.prefix}${sprintf(message, ...args)}` + const log = args.length > 0 ? `${this.prefix}${sprintf(message, ...args)}` + : `${this.prefix}${message}`; + this.logHandler.log(level, log); } - private log(level: LogLevel, message: string | Error, ...args: any[]): void { + private log(level: LogLevel, message: string | Error, args: any[]): void { if (level < this.level) { return; } diff --git a/lib/logging/logger_factory.spec.ts b/lib/logging/logger_factory.spec.ts new file mode 100644 index 000000000..b39524c6e --- /dev/null +++ b/lib/logging/logger_factory.spec.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./logger', async (importOriginal) => { + const actual = await importOriginal() + + const MockLogger = vi.fn(); + const MockConsoleLogHandler = vi.fn(); + + return { ...actual as any, OptimizelyLogger: MockLogger, ConsoleLogHandler: MockConsoleLogHandler }; +}); + +import { OptimizelyLogger, ConsoleLogHandler, LogLevel } from './logger'; +import { createLogger, extractLogger, InfoLog } from './logger_factory'; +import { errorResolver, infoResolver } from '../message/message_resolver'; + +describe('create', () => { + const MockedOptimizelyLogger = vi.mocked(OptimizelyLogger); + const MockedConsoleLogHandler = vi.mocked(ConsoleLogHandler); + + beforeEach(() => { + MockedConsoleLogHandler.mockClear(); + MockedOptimizelyLogger.mockClear(); + }); + + it('should use the passed in options and a default name Optimizely', () => { + const mockLogHandler = { log: vi.fn() }; + + const logger = extractLogger(createLogger({ + level: InfoLog, + logHandler: mockLogHandler, + })); + + expect(logger).toBe(MockedOptimizelyLogger.mock.instances[0]); + const { name, level, infoMsgResolver, errorMsgResolver, logHandler } = MockedOptimizelyLogger.mock.calls[0][0]; + expect(name).toBe('Optimizely'); + expect(level).toBe(LogLevel.Info); + expect(infoMsgResolver).toBe(infoResolver); + expect(errorMsgResolver).toBe(errorResolver); + expect(logHandler).toBe(mockLogHandler); + }); + + it('should use a ConsoleLogHandler if no logHandler is provided', () => { + const logger = extractLogger(createLogger({ + level: InfoLog, + })); + + expect(logger).toBe(MockedOptimizelyLogger.mock.instances[0]); + const { logHandler } = MockedOptimizelyLogger.mock.calls[0][0]; + expect(logHandler).toBe(MockedConsoleLogHandler.mock.instances[0]); + }); +}); \ No newline at end of file diff --git a/lib/logging/logger_factory.ts b/lib/logging/logger_factory.ts index 37e68a801..09d3440fc 100644 --- a/lib/logging/logger_factory.ts +++ b/lib/logging/logger_factory.ts @@ -1,20 +1,102 @@ -// import { LogLevel, LogResolver } from './logger'; +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConsoleLogHandler, LogHandler, LogLevel, OptimizelyLogger } from './logger'; +import { errorResolver, infoResolver, MessageResolver } from '../message/message_resolver'; -// type LevelPreset = { -// level: LogLevel; -// resolver?: LogResolver; -// } +type LevelPreset = { + level: LogLevel, + infoResolver?: MessageResolver, + errorResolver: MessageResolver, +} -// const levelPresetSymbol = Symbol('levelPreset'); +const debugPreset: LevelPreset = { + level: LogLevel.Debug, + infoResolver, + errorResolver, +}; -// export type OpaqueLevelPreset = { -// [levelPresetSymbol]: unknown; -// }; +const infoPreset: LevelPreset = { + level: LogLevel.Info, + infoResolver, + errorResolver, +} -// const Info: LevelPreset = { -// level: LogLevel.Info, -// }; +const warnPreset: LevelPreset = { + level: LogLevel.Warn, + errorResolver, +} -// export const InfoLog: OpaqueLevelPreset = { -// [levelPresetSymbol]: Info, -// }; +const errorPreset: LevelPreset = { + level: LogLevel.Error, + errorResolver, +} + +const levelPresetSymbol = Symbol(); + +export type OpaqueLevelPreset = { + [levelPresetSymbol]: unknown; +}; + +export const DebugLog: OpaqueLevelPreset = { + [levelPresetSymbol]: debugPreset, +}; + +export const InfoLog: OpaqueLevelPreset = { + [levelPresetSymbol]: infoPreset, +}; + +export const WarnLog: OpaqueLevelPreset = { + [levelPresetSymbol]: warnPreset, +}; + +export const ErrorLog: OpaqueLevelPreset = { + [levelPresetSymbol]: errorPreset, +}; + +export const extractLevelPreset = (preset: OpaqueLevelPreset): LevelPreset => { + return preset[levelPresetSymbol] as LevelPreset; +} + +const loggerSymbol = Symbol(); + +export type OpaqueLogger = { + [loggerSymbol]: unknown; +}; + +export type LoggerConfig = { + level: OpaqueLevelPreset, + logHandler?: LogHandler, +}; + +export const createLogger = (config: LoggerConfig): OpaqueLogger => { + const { level, infoResolver, errorResolver } = extractLevelPreset(config.level); + + const loggerName = 'Optimizely'; + + return { + [loggerSymbol]: new OptimizelyLogger({ + name: loggerName, + level, + infoMsgResolver: infoResolver, + errorMsgResolver: errorResolver, + logHandler: config.logHandler || new ConsoleLogHandler(), + }), + }; +}; + +export const extractLogger = (logger: OpaqueLogger): OptimizelyLogger => { + return logger[loggerSymbol] as OptimizelyLogger; +} diff --git a/lib/service.ts b/lib/service.ts index a43bcadc0..03e23ee67 100644 --- a/lib/service.ts +++ b/lib/service.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LoggerFacade, LogLevel } from './logging/logger' +import { LoggerFacade, LogLevel, LogLevelToLower } from './logging/logger' import { resolvablePromise, ResolvablePromise } from "./utils/promise/resolvablePromise"; @@ -86,7 +86,7 @@ export abstract class BaseService implements Service { } for (const { level, message, params } of this.startupLogs) { - const methodName = LogLevel[level].toLowerCase(); + const methodName: string = LogLevelToLower[level]; const method = this.logger[methodName as keyof LoggerFacade]; method.call(this.logger, message, ...params); } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index b38f096cc..725d84090 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -40,11 +40,15 @@ import { EventDispatcher } from './event_processor/event_dispatcher/event_dispat import { EventProcessor } from './event_processor/event_processor'; import { VuidManager } from './vuid/vuid_manager'; import { ErrorNotifier } from './error/error_notifier'; +import { OpaqueLogger } from './logging/logger_factory'; +import { OpaqueErrorNotifier } from './error/error_notifier_factory'; export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; export { EventProcessor } from './event_processor/event_processor'; export { NotificationCenter } from './notification_center'; export { VuidManager } from './vuid/vuid_manager'; +export { OpaqueLogger } from './logging/logger_factory'; +export { OpaqueErrorNotifier } from './error/error_notifier_factory'; export interface BucketerParams { experimentId: string; @@ -365,18 +369,13 @@ export interface TrackListenerPayload extends ListenerPayload { */ export interface Config { projectConfigManager: ProjectConfigManager; - // errorHandler object for logging error - errorHandler?: ErrorHandler; - // event processor eventProcessor?: EventProcessor; - // event dispatcher to use when closing - closingEventDispatcher?: EventDispatcher; // The object to validate against the schema jsonSchemaValidator?: { validate(jsonObject: unknown): boolean; }; - // LogHandler object for logging - logger?: LoggerFacade; + logger?: OpaqueLogger; + errorNotifier?: OpaqueErrorNotifier; // user profile that contains user information userProfileService?: UserProfileService; // dafault options for decide API From 3d2523d1d1fd538eaaf4b3d7b207126fdd23f979 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 21 Jan 2025 22:38:55 +0600 Subject: [PATCH 113/200] [FSSDK-11035] refactor thrown exceptions to use OptimizelyException (#986) --- lib/common_exports.ts | 1 - lib/core/audience_evaluator/index.tests.js | 1 - lib/core/audience_evaluator/index.ts | 5 - .../odp_segment_condition_evaluator/index.ts | 2 - lib/core/bucketer/index.tests.js | 12 +-- lib/core/bucketer/index.ts | 16 +--- .../index.ts | 2 - lib/core/decision_service/index.tests.js | 32 ++++--- lib/core/decision_service/index.ts | 91 ++++++++---------- lib/error_messages.ts | 65 ++++++++----- lib/event_processor/batch_event_processor.ts | 8 +- .../event_dispatcher/default_dispatcher.ts | 5 +- .../send_beacon_dispatcher.browser.ts | 5 +- .../forwarding_event_processor.ts | 5 +- lib/exception_messages.ts | 38 -------- lib/index.browser.tests.js | 4 +- lib/index.browser.ts | 2 - lib/index.node.tests.js | 3 +- lib/log_messages.ts | 28 +----- lib/notification_center/index.ts | 7 -- .../odp_event_api_manager.spec.ts | 5 +- .../event_manager/odp_event_manager.spec.ts | 5 +- lib/odp/event_manager/odp_event_manager.ts | 9 +- lib/odp/odp_manager.spec.ts | 3 +- lib/odp/odp_manager.ts | 5 +- lib/optimizely/index.tests.js | 95 +++++++++---------- lib/optimizely/index.ts | 26 +++-- .../polling_datafile_manager.ts | 13 ++- lib/project_config/project_config.tests.js | 30 ++++-- lib/project_config/project_config.ts | 22 ++--- lib/project_config/project_config_manager.ts | 11 +-- lib/utils/attributes_validator/index.tests.js | 21 ++-- lib/utils/attributes_validator/index.ts | 8 +- lib/utils/config_validator/index.tests.js | 31 +++--- lib/utils/config_validator/index.ts | 17 ++-- lib/utils/event_tag_utils/index.ts | 2 - lib/utils/event_tags_validator/index.tests.js | 15 +-- lib/utils/event_tags_validator/index.ts | 6 +- lib/utils/executor/backoff_retry_runner.ts | 5 +- .../request_handler.browser.ts | 3 +- .../request_handler.node.ts | 10 +- .../json_schema_validator/index.tests.js | 5 +- lib/utils/json_schema_validator/index.ts | 14 +-- lib/utils/semantic_version/index.ts | 2 - lib/utils/type.ts | 2 + .../index.tests.js | 45 ++++----- .../user_profile_service_validator/index.ts | 9 +- lib/vuid/vuid_manager_factory.node.spec.ts | 2 +- lib/vuid/vuid_manager_factory.node.ts | 3 +- 49 files changed, 343 insertions(+), 413 deletions(-) delete mode 100644 lib/exception_messages.ts diff --git a/lib/common_exports.ts b/lib/common_exports.ts index c043796df..583f1b455 100644 --- a/lib/common_exports.ts +++ b/lib/common_exports.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export { LOG_LEVEL } from './utils/enums'; export { createStaticProjectConfigManager } from './project_config/config_manager_factory'; export { PollingConfigManagerConfig } from './project_config/config_manager_factory'; diff --git a/lib/core/audience_evaluator/index.tests.js b/lib/core/audience_evaluator/index.tests.js index 6ab30ca08..bc725a428 100644 --- a/lib/core/audience_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/index.tests.js @@ -21,7 +21,6 @@ import AudienceEvaluator, { createAudienceEvaluator } from './index'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from '../../log_messages'; -// import { getEvaluator } from '../custom_attribute_condition_evaluator'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var mockLogger = { diff --git a/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts index e110ab569..4ada47bbe 100644 --- a/lib/core/audience_evaluator/index.ts +++ b/lib/core/audience_evaluator/index.ts @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - LOG_LEVEL, -} from '../../utils/enums'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; import * as odpSegmentsConditionEvaluator from './odp_segment_condition_evaluator'; @@ -24,8 +21,6 @@ import { CONDITION_EVALUATOR_ERROR, UNKNOWN_CONDITION_TYPE } from '../../error_m import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE} from '../../log_messages'; import { LoggerFacade } from '../../logging/logger'; -const MODULE_NAME = 'AUDIENCE_EVALUATOR'; - export class AudienceEvaluator { private logger?: LoggerFacade; diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts index 4984dce51..d97ee9db5 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts @@ -17,8 +17,6 @@ import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; import { LoggerFacade } from '../../../logging/logger'; import { Condition, OptimizelyUserContext } from '../../../shared_types'; -const MODULE_NAME = 'ODP_SEGMENT_CONDITION_EVALUATOR'; - const QUALIFIED_MATCH_TYPE = 'qualified'; const MATCH_TYPES = [ diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index c87bb35d4..12bea0d3a 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -29,6 +29,7 @@ import { USER_NOT_IN_ANY_EXPERIMENT, USER_ASSIGNED_TO_EXPERIMENT_BUCKET, } from '.'; +import { OptimizelyError } from '../../error/optimizly_error'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var testData = getTestProjectConfig(); @@ -204,9 +205,11 @@ describe('lib/core/bucketer', function () { var bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; - assert.throws(function () { + const ex = assert.throws(function () { bucketer.bucket(bucketerParamsWithInvalidGroupId); - }, sprintf(INVALID_GROUP_ID, 'BUCKETER', '6969')); + }); + assert.equal(ex.baseMessage, INVALID_GROUP_ID); + assert.deepEqual(ex.params, ['6969']); }); }); @@ -343,10 +346,7 @@ describe('lib/core/bucketer', function () { const response = assert.throws(function() { bucketer._generateBucketValue(null); } ); - expect([ - sprintf(INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read property 'length' of null"), // node v14 - sprintf(INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read properties of null (reading \'length\')") // node v16 - ]).contain(response.message); + expect(response.baseMessage).to.equal(INVALID_BUCKETING_ID); }); }); diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index 88df2e818..6d23856e5 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -17,7 +17,6 @@ /** * Bucketer API for determining the variation id from the specified parameters */ -import { sprintf } from '../../utils/fns'; import murmurhash from 'murmurhash'; import { LoggerFacade } from '../../logging/logger'; import { @@ -27,8 +26,8 @@ import { Group, } from '../../shared_types'; -import { LOG_LEVEL } from '../../utils/enums'; import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.'; export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.'; @@ -39,7 +38,6 @@ export const INVALID_VARIATION_ID = 'Bucketed into an invalid variation ID. Retu const HASH_SEED = 1; const MAX_HASH_VALUE = Math.pow(2, 32); const MAX_TRAFFIC_VALUE = 10000; -const MODULE_NAME = 'BUCKETER'; const RANDOM_POLICY = 'random'; /** @@ -66,7 +64,7 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (groupId) { const group = bucketerParams.groupIdMap[groupId]; if (!group) { - throw new Error(sprintf(INVALID_GROUP_ID, MODULE_NAME, groupId)); + throw new OptimizelyError(INVALID_GROUP_ID, groupId); } if (group.policy === RANDOM_POLICY) { const bucketedExperimentId = bucketUserIntoExperiment( @@ -85,7 +83,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_NOT_IN_ANY_EXPERIMENT, - MODULE_NAME, bucketerParams.userId, groupId, ]); @@ -105,7 +102,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -125,7 +121,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -142,7 +137,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_ASSIGNED_TO_EXPERIMENT_BUCKET, - MODULE_NAME, bucketValue, bucketerParams.userId, ]); @@ -151,8 +145,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (entityId !== null) { if (!bucketerParams.variationIdMap[entityId]) { if (entityId) { - bucketerParams.logger?.warn(INVALID_VARIATION_ID, MODULE_NAME); - decideReasons.push([INVALID_VARIATION_ID, MODULE_NAME]); + bucketerParams.logger?.warn(INVALID_VARIATION_ID); + decideReasons.push([INVALID_VARIATION_ID]); } return { result: null, @@ -228,7 +222,7 @@ export const _generateBucketValue = function(bucketingKey: string): number { const ratio = hashValue / MAX_HASH_VALUE; return Math.floor(ratio * MAX_TRAFFIC_VALUE); } catch (ex: any) { - throw new Error(sprintf(INVALID_BUCKETING_ID, MODULE_NAME, bucketingKey, ex.message)); + throw new OptimizelyError(INVALID_BUCKETING_ID, bucketingKey, ex.message); } }; diff --git a/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts index 0a3c0b0a6..c722c1837 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/lib/core/custom_attribute_condition_evaluator/index.ts @@ -29,8 +29,6 @@ import { } from '../../error_messages'; import { LoggerFacade } from '../../logging/logger'; -const MODULE_NAME = 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR'; - const EXACT_MATCH_TYPE = 'exact'; const EXISTS_MATCH_TYPE = 'exists'; const GREATER_OR_EQUAL_THAN_MATCH_TYPE = 'ge'; diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 39d8889fd..470d998eb 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -39,27 +39,31 @@ import { getTestProjectConfig, getTestProjectConfigWithFeatures, } from '../../tests/test_data'; + import { - AUDIENCE_EVALUATION_RESULT_COMBINED, - EVALUATING_AUDIENCES_COMBINED, - USER_FORCED_IN_VARIATION, USER_HAS_NO_FORCED_VARIATION, - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_IN_EXPERIMENT, + VALID_BUCKETING_ID, + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND, +} from '../../log_messages'; + +import { EXPERIMENT_NOT_RUNNING, RETURNING_STORED_VARIATION, + USER_NOT_IN_EXPERIMENT, + USER_FORCED_IN_VARIATION, + EVALUATING_AUDIENCES_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_IN_ROLLOUT, + USER_NOT_IN_ROLLOUT, FEATURE_HAS_NO_EXPERIMENTS, - NO_ROLLOUT_EXISTS, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, USER_BUCKETED_INTO_TARGETING_RULE, - USER_IN_ROLLOUT, + NO_ROLLOUT_EXISTS, USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_BUCKETED_INTO_TARGETING_RULE, - USER_NOT_IN_ROLLOUT, - VALID_BUCKETING_ID, - SAVED_USER_VARIATION, - SAVED_VARIATION_NOT_FOUND -} from '../../log_messages'; -import { mock } from 'node:test'; +} from '../decision_service/index'; + import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from '../../error_messages'; var testData = getTestProjectConfig(); diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 7ce6a1c85..9867d9b19 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -14,15 +14,11 @@ * limitations under the License. */ import { LoggerFacade } from '../../logging/logger' -import { sprintf } from '../../utils/fns'; - -import fns from '../../utils/fns'; import { bucket } from '../bucketer'; import { AUDIENCE_EVALUATION_TYPES, CONTROL_ATTRIBUTES, DECISION_SOURCES, - LOG_LEVEL, } from '../../utils/enums'; import { getAudiencesById, @@ -53,51 +49,55 @@ import { Variation, } from '../../shared_types'; import { - IMPROPERLY_FORMATTED_EXPERIMENT, - INVALID_ROLLOUT_ID, INVALID_USER_ID, INVALID_VARIATION_KEY, NO_VARIATION_FOR_EXPERIMENT_KEY, USER_NOT_IN_FORCED_VARIATION, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR, - FORCED_BUCKETING_FAILED, BUCKETING_ID_NOT_STRING, } from '../../error_messages'; import { - AUDIENCE_EVALUATION_RESULT_COMBINED, - EVALUATING_AUDIENCES_COMBINED, - EXPERIMENT_NOT_RUNNING, - FEATURE_HAS_NO_EXPERIMENTS, - NO_ROLLOUT_EXISTS, - RETURNING_STORED_VARIATION, - ROLLOUT_HAS_NO_EXPERIMENTS, SAVED_USER_VARIATION, SAVED_VARIATION_NOT_FOUND, - USER_BUCKETED_INTO_TARGETING_RULE, - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - USER_FORCED_IN_VARIATION, USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, - USER_HAS_FORCED_VARIATION, USER_HAS_NO_FORCED_VARIATION, - USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - USER_HAS_NO_VARIATION, - USER_HAS_VARIATION, - USER_IN_ROLLOUT, USER_MAPPED_TO_FORCED_VARIATION, - USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_BUCKETED_INTO_TARGETING_RULE, - USER_NOT_IN_EXPERIMENT, - USER_NOT_IN_ROLLOUT, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, VALID_BUCKETING_ID, VARIATION_REMOVED_FOR_USER, } from '../../log_messages'; - -export const MODULE_NAME = 'DECISION_SERVICE'; +import { OptimizelyError } from '../../error/optimizly_error'; + +export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; +export const RETURNING_STORED_VARIATION = + 'Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; +export const USER_NOT_IN_EXPERIMENT = 'User %s does not meet conditions to be in experiment %s.'; +export const USER_HAS_NO_VARIATION = 'User %s is in no variation of experiment %s.'; +export const USER_HAS_VARIATION = 'User %s is in variation %s of experiment %s.'; +export const USER_FORCED_IN_VARIATION = 'User %s is forced in variation %s.'; +export const FORCED_BUCKETING_FAILED = 'Variation key %s is not in datafile. Not activating user %s.'; +export const EVALUATING_AUDIENCES_COMBINED = 'Evaluating audiences for %s "%s": %s.'; +export const AUDIENCE_EVALUATION_RESULT_COMBINED = 'Audiences for %s %s collectively evaluated to %s.'; +export const USER_IN_ROLLOUT = 'User %s is in rollout of feature %s.'; +export const USER_NOT_IN_ROLLOUT = 'User %s is not in rollout of feature %s.'; +export const FEATURE_HAS_NO_EXPERIMENTS = 'Feature %s is not attached to any experiments.'; +export const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = + 'User %s does not meet conditions for targeting rule %s.'; +export const USER_NOT_BUCKETED_INTO_TARGETING_RULE = +'User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; +export const USER_BUCKETED_INTO_TARGETING_RULE = 'User %s bucketed into targeting rule %s.'; +export const NO_ROLLOUT_EXISTS = 'There is no rollout of feature %s.'; +export const INVALID_ROLLOUT_ID = 'Invalid rollout ID %s attached to feature %s'; +export const ROLLOUT_HAS_NO_EXPERIMENTS = 'Rollout of feature %s has no experiments'; +export const IMPROPERLY_FORMATTED_EXPERIMENT = 'Experiment key %s is improperly formatted.'; +export const USER_HAS_FORCED_VARIATION = + 'Variation %s is mapped to experiment %s and user %s in the forced variation map.'; +export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = 'User %s meets conditions for targeting rule %s.'; export interface DecisionObj { experiment: Experiment | null; @@ -172,7 +172,7 @@ export class DecisionService { const experimentKey = experiment.key; if (!this.checkIfExperimentIsActive(configObj, experimentKey)) { this.logger?.info(EXPERIMENT_NOT_RUNNING, experimentKey); - decideReasons.push([EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey]); + decideReasons.push([EXPERIMENT_NOT_RUNNING, experimentKey]); return { result: null, reasons: decideReasons, @@ -211,7 +211,6 @@ export class DecisionService { ); decideReasons.push([ RETURNING_STORED_VARIATION, - MODULE_NAME, variation.key, experimentKey, userId, @@ -240,7 +239,6 @@ export class DecisionService { ); decideReasons.push([ USER_NOT_IN_EXPERIMENT, - MODULE_NAME, userId, experimentKey, ]); @@ -265,7 +263,6 @@ export class DecisionService { ); decideReasons.push([ USER_HAS_NO_VARIATION, - MODULE_NAME, userId, experimentKey, ]); @@ -283,7 +280,6 @@ export class DecisionService { ); decideReasons.push([ USER_HAS_VARIATION, - MODULE_NAME, userId, variation.key, experimentKey, @@ -381,7 +377,6 @@ export class DecisionService { ); decideReasons.push([ USER_FORCED_IN_VARIATION, - MODULE_NAME, userId, forcedVariationKey, ]); @@ -392,13 +387,11 @@ export class DecisionService { } else { this.logger?.error( FORCED_BUCKETING_FAILED, - MODULE_NAME, forcedVariationKey, userId, ); decideReasons.push([ FORCED_BUCKETING_FAILED, - MODULE_NAME, forcedVariationKey, userId, ]); @@ -444,7 +437,6 @@ export class DecisionService { ); decideReasons.push([ EVALUATING_AUDIENCES_COMBINED, - MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, JSON.stringify(experimentAudienceConditions), @@ -458,7 +450,6 @@ export class DecisionService { ); decideReasons.push([ AUDIENCE_EVALUATION_RESULT_COMBINED, - MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, result.toString().toUpperCase(), @@ -653,10 +644,10 @@ export class DecisionService { if (rolloutDecision.variation) { this.logger?.debug(USER_IN_ROLLOUT, userId, feature.key); - decideReasons.push([USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + decideReasons.push([USER_IN_ROLLOUT, userId, feature.key]); } else { this.logger?.debug(USER_NOT_IN_ROLLOUT, userId, feature.key); - decideReasons.push([USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + decideReasons.push([USER_NOT_IN_ROLLOUT, userId, feature.key]); } decisions.push({ @@ -741,7 +732,7 @@ export class DecisionService { } } else { this.logger?.debug(FEATURE_HAS_NO_EXPERIMENTS, feature.key); - decideReasons.push([FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key]); + decideReasons.push([FEATURE_HAS_NO_EXPERIMENTS, feature.key]); } variationForFeatureExperiment = { @@ -765,7 +756,7 @@ export class DecisionService { let decisionObj: DecisionObj; if (!feature.rolloutId) { this.logger?.debug(NO_ROLLOUT_EXISTS, feature.key); - decideReasons.push([NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key]); + decideReasons.push([NO_ROLLOUT_EXISTS, feature.key]); decisionObj = { experiment: null, variation: null, @@ -785,7 +776,7 @@ export class DecisionService { feature.rolloutId, feature.key, ); - decideReasons.push([INVALID_ROLLOUT_ID, MODULE_NAME, feature.rolloutId, feature.key]); + decideReasons.push([INVALID_ROLLOUT_ID, feature.rolloutId, feature.key]); decisionObj = { experiment: null, variation: null, @@ -803,7 +794,7 @@ export class DecisionService { ROLLOUT_HAS_NO_EXPERIMENTS, feature.rolloutId, ); - decideReasons.push([ROLLOUT_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.rolloutId]); + decideReasons.push([ROLLOUT_HAS_NO_EXPERIMENTS, feature.rolloutId]); decisionObj = { experiment: null, variation: null, @@ -975,7 +966,7 @@ export class DecisionService { */ removeForcedVariation(userId: string, experimentId: string, experimentKey: string): void { if (!userId) { - throw new Error(sprintf(INVALID_USER_ID, MODULE_NAME)); + throw new OptimizelyError(INVALID_USER_ID); } if (this.forcedVariationMap.hasOwnProperty(userId)) { @@ -986,7 +977,7 @@ export class DecisionService { userId, ); } else { - throw new Error(sprintf(USER_NOT_IN_FORCED_VARIATION, MODULE_NAME, userId)); + throw new OptimizelyError(USER_NOT_IN_FORCED_VARIATION, userId); } } @@ -1049,12 +1040,10 @@ export class DecisionService { // catching improperly formatted experiments this.logger?.error( IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, experimentKey, ); decideReasons.push([ IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, experimentKey, ]); @@ -1078,7 +1067,6 @@ export class DecisionService { if (!variationId) { this.logger?.debug( USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - MODULE_NAME, experimentKey, userId, ); @@ -1098,7 +1086,6 @@ export class DecisionService { ); decideReasons.push([ USER_HAS_FORCED_VARIATION, - MODULE_NAME, variationKey, experimentKey, userId, @@ -1266,7 +1253,6 @@ export class DecisionService { ); decideReasons.push([ USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ]); @@ -1286,7 +1272,6 @@ export class DecisionService { ); decideReasons.push([ USER_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, userId, loggingKey]); } else if (!everyoneElse) { @@ -1298,7 +1283,6 @@ export class DecisionService { ); decideReasons.push([ USER_NOT_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ]); @@ -1314,7 +1298,6 @@ export class DecisionService { ); decideReasons.push([ USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ]); diff --git a/lib/error_messages.ts b/lib/error_messages.ts index 75f869c2e..7ef14178d 100644 --- a/lib/error_messages.ts +++ b/lib/error_messages.ts @@ -18,33 +18,31 @@ export const BROWSER_ODP_MANAGER_INITIALIZATION_FAILED = '%s: Error initializing export const CONDITION_EVALUATOR_ERROR = 'Error evaluating audience condition of type %s: %s'; export const DATAFILE_AND_SDK_KEY_MISSING = '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely'; -export const EXPERIMENT_KEY_NOT_IN_DATAFILE = '%s: Experiment key %s is not in datafile.'; +export const EXPERIMENT_KEY_NOT_IN_DATAFILE = 'Experiment key %s is not in datafile.'; export const FEATURE_NOT_IN_DATAFILE = 'Feature key %s is not in datafile.'; export const FETCH_SEGMENTS_FAILED_NETWORK_ERROR = '%s: Audience segments fetch failed. (network error)'; export const FETCH_SEGMENTS_FAILED_DECODE_ERROR = '%s: Audience segments fetch failed. (decode error)'; -export const IMPROPERLY_FORMATTED_EXPERIMENT = 'Experiment key %s is improperly formatted.'; -export const INVALID_ATTRIBUTES = '%s: Provided attributes are in an invalid format.'; -export const INVALID_BUCKETING_ID = '%s: Unable to generate hash for bucketing ID %s: %s'; -export const INVALID_DATAFILE = '%s: Datafile is invalid - property %s: %s'; -export const INVALID_DATAFILE_MALFORMED = '%s: Datafile is invalid because it is malformed.'; -export const INVALID_CONFIG = '%s: Provided Optimizely config is in an invalid format.'; -export const INVALID_JSON = '%s: JSON object is not valid.'; -export const INVALID_ERROR_HANDLER = '%s: Provided "errorHandler" is in an invalid format.'; -export const INVALID_EVENT_DISPATCHER = '%s: Provided "eventDispatcher" is in an invalid format.'; -export const INVALID_EVENT_TAGS = '%s: Provided event tags are in an invalid format.'; +export const INVALID_ATTRIBUTES = 'Provided attributes are in an invalid format.'; +export const INVALID_BUCKETING_ID = 'Unable to generate hash for bucketing ID %s: %s'; +export const INVALID_DATAFILE = 'Datafile is invalid - property %s: %s'; +export const INVALID_DATAFILE_MALFORMED = 'Datafile is invalid because it is malformed.'; +export const INVALID_CONFIG = 'Provided Optimizely config is in an invalid format.'; +export const INVALID_JSON = 'JSON object is not valid.'; +export const INVALID_ERROR_HANDLER = 'Provided "errorHandler" is in an invalid format.'; +export const INVALID_EVENT_DISPATCHER = 'Provided "eventDispatcher" is in an invalid format.'; +export const INVALID_EVENT_TAGS = 'Provided event tags are in an invalid format.'; export const INVALID_EXPERIMENT_KEY = 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; export const INVALID_EXPERIMENT_ID = 'Experiment ID %s is not in datafile.'; -export const INVALID_GROUP_ID = '%s: Group ID %s is not in datafile.'; -export const INVALID_LOGGER = '%s: Provided "logger" is in an invalid format.'; -export const INVALID_ROLLOUT_ID = 'Invalid rollout ID %s attached to feature %s'; -export const INVALID_USER_ID = '%s: Provided user ID is in an invalid format.'; -export const INVALID_USER_PROFILE_SERVICE = '%s: Provided user profile service instance is in an invalid format: %s.'; +export const INVALID_GROUP_ID = 'Group ID %s is not in datafile.'; +export const INVALID_LOGGER = 'Provided "logger" is in an invalid format.'; +export const INVALID_USER_ID = 'Provided user ID is in an invalid format.'; +export const INVALID_USER_PROFILE_SERVICE = 'Provided user profile service instance is in an invalid format: %s.'; export const LOCAL_STORAGE_DOES_NOT_EXIST = 'Error accessing window localStorage.'; export const MISSING_INTEGRATION_KEY = - '%s: Integration key missing from datafile. All integrations should include a key.'; -export const NO_DATAFILE_SPECIFIED = '%s: No datafile specified. Cannot start optimizely.'; -export const NO_JSON_PROVIDED = '%s: No JSON object to validate against schema.'; + 'Integration key missing from datafile. All integrations should include a key.'; +export const NO_DATAFILE_SPECIFIED = 'No datafile specified. Cannot start optimizely.'; +export const NO_JSON_PROVIDED = 'No JSON object to validate against schema.'; export const NO_EVENT_PROCESSOR = 'No event processor is provided'; export const NO_VARIATION_FOR_EXPERIMENT_KEY = 'No variation key %s defined in datafile for experiment %s.'; export const ODP_CONFIG_NOT_AVAILABLE = '%s: ODP is not integrated to the project.'; @@ -79,21 +77,21 @@ export const ODP_VUID_INITIALIZATION_FAILED = '%s: ODP VUID initialization faile export const ODP_VUID_REGISTRATION_FAILED = '%s: ODP VUID failed to be registered.'; export const ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING = '%s: ODP register vuid failed. (Event Manager not instantiated).'; -export const UNDEFINED_ATTRIBUTE = '%s: Provided attribute: %s has an undefined value.'; +export const UNDEFINED_ATTRIBUTE = 'Provided attribute: %s has an undefined value.'; export const UNRECOGNIZED_ATTRIBUTE = 'Unrecognized attribute %s provided. Pruning before sending event to Optimizely.'; export const UNABLE_TO_CAST_VALUE = 'Unable to cast value %s to type %s, returning null.'; export const USER_NOT_IN_FORCED_VARIATION = - '%s: User %s is not in the forced variation map. Cannot remove their forced variation.'; + 'User %s is not in the forced variation map. Cannot remove their forced variation.'; export const USER_PROFILE_LOOKUP_ERROR = 'Error while looking up user profile for user ID "%s": %s.'; export const USER_PROFILE_SAVE_ERROR = 'Error while saving user profile for user ID "%s": %s.'; export const VARIABLE_KEY_NOT_IN_DATAFILE = '%s: Variable with key "%s" associated with feature with key "%s" is not in datafile.'; export const VARIATION_ID_NOT_IN_DATAFILE = '%s: No variation ID %s defined in datafile for experiment %s.'; export const VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT = 'Variation ID %s is not in the datafile.'; -export const INVALID_INPUT_FORMAT = '%s: Provided %s is in an invalid format.'; +export const INVALID_INPUT_FORMAT = 'Provided %s is in an invalid format.'; export const INVALID_DATAFILE_VERSION = - '%s: This version of the JavaScript SDK does not support the given datafile version: %s'; + 'This version of the JavaScript SDK does not support the given datafile version: %s'; export const INVALID_VARIATION_KEY = 'Provided variation key is in an invalid format.'; export const UNABLE_TO_GET_VUID = 'Unable to get VUID - ODP Manager is not instantiated yet.'; export const ERROR_FETCHING_DATAFILE = 'Error fetching datafile: %s'; @@ -114,7 +112,6 @@ export const VARIABLE_REQUESTED_WITH_WRONG_TYPE = 'Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.'; export const UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX = 'Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.'; -export const FORCED_BUCKETING_FAILED = 'Variation key %s is not in datafile. Not activating user %s.'; export const BUCKETING_ID_NOT_STRING = 'BucketingID attribute is not a string. Defaulted to userId'; export const UNEXPECTED_CONDITION_VALUE = 'Audience condition %s evaluated to UNKNOWN because the condition value is not supported.'; @@ -126,5 +123,25 @@ export const REQUEST_TIMEOUT = 'Request timeout'; export const REQUEST_ERROR = 'Request error'; export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; +export const ONREADY_TIMEOUT = 'onReady timeout expired after %s ms'; +export const INSTANCE_CLOSED = 'Instance closed'; +export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it could be started'; +export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; +export const NO_SDKKEY_OR_DATAFILE = 'At least one of sdkKey or datafile must be provided'; +export const RETRY_CANCELLED = 'Retry cancelled'; +export const SERVICE_STOPPED_BEFORE_IT_WAS_STARTED = 'Service stopped before it was started'; +export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; +export const SEND_BEACON_FAILED = 'sendBeacon failed'; +export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' +export const FAILED_TO_DISPATCH_EVENTS_WITH_ARG = 'Failed to dispatch events: %s'; +export const EVENT_PROCESSOR_STOPPED = 'Event processor stopped before it could be started'; +export const CANNOT_START_WITHOUT_ODP_CONFIG = 'cannot start without ODP config'; +export const START_CALLED_WHEN_ODP_IS_NOT_INTEGRATED = 'start() called when ODP is not integrated'; +export const ODP_ACTION_IS_NOT_VALID = 'ODP action is not valid (cannot be empty).'; +export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before running'; +export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; +export const ONREADY_TIMEOUT_EXPIRED = 'onReady timeout expired after %s ms'; +export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; + export const messages: string[] = []; diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index a6eee569c..dae605d88 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -27,8 +27,8 @@ import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; import { EventEmitter } from "../utils/event_emitter/event_emitter"; import { IdGenerator } from "../utils/id_generator"; import { areEventContextsEqual } from "./event_builder/user_event"; -import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "../exception_messages"; -import { sprintf } from "../utils/fns"; +import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "../error_messages"; +import { OptimizelyError } from "../error/optimizly_error"; export const DEFAULT_MIN_BACKOFF = 1000; export const DEFAULT_MAX_BACKOFF = 32000; @@ -165,7 +165,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { const dispatcher = closing && this.closingEventDispatcher ? this.closingEventDispatcher : this.eventDispatcher; return dispatcher.dispatchEvent(request).then((res) => { if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - return Promise.reject(new Error(sprintf(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode))); + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode)); } return Promise.resolve(res); }); @@ -276,7 +276,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new Error(EVENT_PROCESSOR_STOPPED)); + this.startPromise.reject(new OptimizelyError(EVENT_PROCESSOR_STOPPED)); } this.state = ServiceState.Stopping; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.ts b/lib/event_processor/event_dispatcher/default_dispatcher.ts index 21c42bc5e..a812541cd 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from '../../exception_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from '../../error_messages'; import { RequestHandler } from '../../utils/http_request_handler/http'; import { EventDispatcher, EventDispatcherResponse, LogEvent } from './event_dispatcher'; @@ -29,7 +30,7 @@ export class DefaultEventDispatcher implements EventDispatcher { ): Promise<EventDispatcherResponse> { // Non-POST requests not supported if (eventObj.httpVerb !== 'POST') { - return Promise.reject(new Error(ONLY_POST_REQUESTS_ARE_SUPPORTED)); + return Promise.reject(new OptimizelyError(ONLY_POST_REQUESTS_ARE_SUPPORTED)); } const dataString = JSON.stringify(eventObj.params); diff --git a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts index 605bae2ef..d3130342a 100644 --- a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts +++ b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { SEND_BEACON_FAILED } from '../../exception_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { SEND_BEACON_FAILED } from '../../error_messages'; import { EventDispatcher, EventDispatcherResponse } from './event_dispatcher'; export type Event = { @@ -42,7 +43,7 @@ export const dispatchEvent = function( if(success) { return Promise.resolve({}); } - return Promise.reject(new Error(SEND_BEACON_FAILED)); + return Promise.reject(new OptimizelyError(SEND_BEACON_FAILED)); } const eventDispatcher : EventDispatcher = { diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index dbbe7076c..8ac6f6631 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -23,7 +23,8 @@ import { buildLogEvent } from './event_builder/log_event'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; -import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from '../exception_messages'; +import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from '../error_messages'; +import { OptimizelyError } from '../error/optimizly_error'; class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; private eventEmitter: EventEmitter<{ dispatch: LogEvent }>; @@ -55,7 +56,7 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new Error(SERVICE_STOPPED_BEFORE_IT_WAS_STARTED)); + this.startPromise.reject(new OptimizelyError(SERVICE_STOPPED_BEFORE_IT_WAS_STARTED)); } this.state = ServiceState.Terminated; diff --git a/lib/exception_messages.ts b/lib/exception_messages.ts deleted file mode 100644 index aa743b905..000000000 --- a/lib/exception_messages.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' -export const FAILED_TO_DISPATCH_EVENTS_WITH_ARG = 'Failed to dispatch events: %s'; -export const EVENT_PROCESSOR_STOPPED = 'Event processor stopped before it could be started'; -export const SERVICE_STOPPED_BEFORE_IT_WAS_STARTED = 'Service stopped before it was started'; -export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; -export const SEND_BEACON_FAILED = 'sendBeacon failed'; -export const CANNOT_START_WITHOUT_ODP_CONFIG = 'cannot start without ODP config'; -export const START_CALLED_WHEN_ODP_IS_NOT_INTEGRATED = 'start() called when ODP is not integrated'; -export const ODP_ACTION_IS_NOT_VALID = 'ODP action is not valid (cannot be empty).'; -export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before running'; -export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; -export const ONREADY_TIMEOUT_EXPIRED = 'onReady timeout expired after %s ms'; -export const INSTANCE_CLOSED = 'Instance closed'; -export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it could be started'; -export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; -export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; -export const FAILED_TO_STOP = 'Failed to stop'; -export const YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE = 'You must provide at least one of sdkKey or datafile'; -export const RETRY_CANCELLED = 'Retry cancelled'; -export const REQUEST_FAILED = 'Request failed'; -export const PROMISE_SHOULD_NOT_HAVE_RESOLVED = 'Promise should not have resolved'; -export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index cc6c34cd0..5fb84a30f 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -22,8 +22,6 @@ import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; -import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; - class MockLocalStorage { store = {}; @@ -134,7 +132,7 @@ describe('javascript-sdk (Browser)', function() { // }); it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); + configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 054c584d8..c25971393 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -19,7 +19,6 @@ import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; -import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; import Optimizely from './optimizely'; import { UserAgentParser } from './odp/ua_parser/user_agent_parser'; @@ -37,7 +36,6 @@ import { LoggerFacade } from './logging/logger'; import { Maybe } from './utils/type'; -const MODULE_NAME = 'INDEX_BROWSER'; const DEFAULT_EVENT_BATCH_SIZE = 10; const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 6d2bba594..f35903418 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -21,7 +21,6 @@ import testData from './tests/test_data'; import optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; -import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; var createLogger = () => ({ debug: () => {}, @@ -70,7 +69,7 @@ describe('optimizelyFactory', function() { // }); it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); + configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), diff --git a/lib/log_messages.ts b/lib/log_messages.ts index d5830cba7..6123adc74 100644 --- a/lib/log_messages.ts +++ b/lib/log_messages.ts @@ -18,16 +18,13 @@ export const ACTIVATE_USER = '%s: Activating user %s in experiment %s.'; export const DISPATCH_CONVERSION_EVENT = '%s: Dispatching conversion event to URL %s with params %s.'; export const DISPATCH_IMPRESSION_EVENT = '%s: Dispatching impression event to URL %s with params %s.'; export const DEPRECATED_EVENT_VALUE = '%s: Event value is deprecated in %s call.'; -export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; export const FEATURE_ENABLED_FOR_USER = 'Feature %s is enabled for user %s.'; export const FEATURE_NOT_ENABLED_FOR_USER = 'Feature %s is not enabled for user %s.'; -export const FEATURE_HAS_NO_EXPERIMENTS = 'Feature %s is not attached to any experiments.'; export const FAILED_TO_PARSE_VALUE = '%s: Failed to parse event value "%s" from event tags.'; export const FAILED_TO_PARSE_REVENUE = 'Failed to parse revenue value "%s" from event tags.'; export const INVALID_CLIENT_ENGINE = 'Invalid client engine passed: %s. Defaulting to node-sdk.'; export const INVALID_DEFAULT_DECIDE_OPTIONS = '%s: Provided default decide options is not an array.'; export const INVALID_DECIDE_OPTIONS = 'Provided decide options is not an array. Using default decide options.'; -export const NO_ROLLOUT_EXISTS = 'There is no rollout of feature %s.'; export const NOT_ACTIVATING_USER = 'Not activating user %s for experiment %s.'; export const ODP_DISABLED = 'ODP Disabled.'; export const ODP_IDENTIFY_FAILED_ODP_DISABLED = '%s: ODP identify event for user %s is not dispatched (ODP disabled).'; @@ -37,9 +34,6 @@ export const ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED = '%s: sendOdpEvent failed to parse through and convert fs_user_id aliases'; export const PARSED_REVENUE_VALUE = 'Parsed revenue value "%s" from event tags.'; export const PARSED_NUMERIC_VALUE = 'Parsed event value "%s" from event tags.'; -export const RETURNING_STORED_VARIATION = - 'Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; -export const ROLLOUT_HAS_NO_EXPERIMENTS = 'Rollout of feature %s has no experiments'; export const SAVED_USER_VARIATION = 'Saved user profile for user "%s".'; export const UPDATED_USER_VARIATION = '%s: Updated variation "%s" of experiment "%s" for user "%s".'; export const SAVED_VARIATION_NOT_FOUND = @@ -47,21 +41,12 @@ export const SAVED_VARIATION_NOT_FOUND = export const SHOULD_NOT_DISPATCH_ACTIVATE = 'Experiment %s is not in "Running" state. Not activating user.'; export const SKIPPING_JSON_VALIDATION = 'Skipping JSON schema validation.'; export const TRACK_EVENT = 'Tracking event %s for user %s.'; -export const USER_BUCKETED_INTO_TARGETING_RULE = 'User %s bucketed into targeting rule %s.'; export const USER_IN_FEATURE_EXPERIMENT = '%s: User %s is in variation %s of experiment %s on the feature %s.'; -export const USER_IN_ROLLOUT = 'User %s is in rollout of feature %s.'; export const USER_NOT_BUCKETED_INTO_EVERYONE_TARGETING_RULE = '%s: User %s not bucketed into everyone targeting rule due to traffic allocation.'; export const USER_NOT_BUCKETED_INTO_ANY_EXPERIMENT_IN_GROUP = '%s: User %s is not in any experiment of group %s.'; -export const USER_NOT_BUCKETED_INTO_TARGETING_RULE = - 'User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; -export const USER_FORCED_IN_VARIATION = 'User %s is forced in variation %s.'; export const USER_MAPPED_TO_FORCED_VARIATION = 'Set variation %s for experiment %s and user %s in the forced variation map.'; -export const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = - 'User %s does not meet conditions for targeting rule %s.'; -export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = 'User %s meets conditions for targeting rule %s.'; -export const USER_HAS_VARIATION = 'User %s is in variation %s of experiment %s.'; export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = @@ -70,14 +55,7 @@ export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; -export const USER_HAS_FORCED_VARIATION = - 'Variation %s is mapped to experiment %s and user %s in the forced variation map.'; -export const USER_HAS_NO_VARIATION = 'User %s is in no variation of experiment %s.'; export const USER_HAS_NO_FORCED_VARIATION = 'User %s is not in the forced variation map.'; -export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = - 'No experiment %s mapped to user %s in the forced variation map.'; -export const USER_NOT_IN_EXPERIMENT = 'User %s does not meet conditions to be in experiment %s.'; -export const USER_NOT_IN_ROLLOUT = 'User %s is not in rollout of feature %s.'; export const USER_RECEIVED_DEFAULT_VARIABLE_VALUE = 'User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".'; export const FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE = @@ -91,9 +69,7 @@ export const VARIATION_REMOVED_FOR_USER = 'Variation mapped to experiment %s has export const VALID_BUCKETING_ID = 'BucketingId is valid: "%s"'; export const EVALUATING_AUDIENCE = 'Starting to evaluate audience "%s" with conditions: %s.'; -export const EVALUATING_AUDIENCES_COMBINED = 'Evaluating audiences for %s "%s": %s.'; export const AUDIENCE_EVALUATION_RESULT = 'Audience "%s" evaluated to %s.'; -export const AUDIENCE_EVALUATION_RESULT_COMBINED = 'Audiences for %s %s collectively evaluated to %s.'; export const MISSING_ATTRIBUTE_VALUE = 'Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".'; export const UNEXPECTED_TYPE_NULL = @@ -104,6 +80,8 @@ export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped hea export const ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN = 'Adding Authorization header with Bearer Token'; export const MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS = 'Making datafile request to url %s with headers: %s'; export const RESPONSE_STATUS_CODE = 'Response status code: %s'; -export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; +export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; +export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = + 'No experiment %s mapped to user %s in the forced variation map.'; export const messages: string[] = []; diff --git a/lib/notification_center/index.ts b/lib/notification_center/index.ts index 15886fde3..2db4d36d7 100644 --- a/lib/notification_center/index.ts +++ b/lib/notification_center/index.ts @@ -14,13 +14,8 @@ * limitations under the License. */ import { LoggerFacade } from '../logging/logger'; -import { ErrorHandler } from '../error/error_handler'; import { objectValues } from '../utils/fns'; -import { - LOG_LEVEL, -} from '../utils/enums'; - import { NOTIFICATION_TYPES } from './type'; import { NotificationType, NotificationPayload } from './type'; import { Consumer, Fn } from '../utils/type'; @@ -29,8 +24,6 @@ import { NOTIFICATION_LISTENER_EXCEPTION } from '../error_messages'; import { ErrorReporter } from '../error/error_reporter'; import { ErrorNotifier } from '../error/error_notifier'; -const MODULE_NAME = 'NOTIFICATION_CENTER'; - interface NotificationCenterOptions { logger?: LoggerFacade; errorNotifier?: ErrorNotifier; diff --git a/lib/odp/event_manager/odp_event_api_manager.spec.ts b/lib/odp/event_manager/odp_event_api_manager.spec.ts index 55ec009e1..316787821 100644 --- a/lib/odp/event_manager/odp_event_api_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_api_manager.spec.ts @@ -41,7 +41,6 @@ const PIXEL_URL = 'https://odp.pixel.com/'; const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; -import { REQUEST_FAILED } from '../../exception_messages'; describe('DefaultOdpEventApiManager', () => { it('should generate the event request using the correct odp config and event', async () => { @@ -101,7 +100,7 @@ describe('DefaultOdpEventApiManager', () => { it('should return a promise that fails if the requestHandler response promise fails', async () => { const mockRequestHandler = getMockRequestHandler(); mockRequestHandler.makeRequest.mockReturnValue({ - responsePromise: Promise.reject(new Error(REQUEST_FAILED)), + responsePromise: Promise.reject(new Error('REQUEST_FAILED')), }); const requestGenerator = vi.fn().mockReturnValue({ method: 'PATCH', @@ -115,7 +114,7 @@ describe('DefaultOdpEventApiManager', () => { const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); const response = manager.sendEvents(odpConfig, ODP_EVENTS); - await expect(response).rejects.toThrow('Request failed'); + await expect(response).rejects.toThrow(); }); it('should return a promise that resolves with correct response code from the requestHandler', async () => { diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index ff7efa5cb..9d16273b9 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -23,7 +23,6 @@ import { OdpEvent } from './odp_event'; import { OdpConfig } from '../odp_config'; import { EventDispatchResponse } from './odp_event_api_manager'; import { advanceTimersByTime } from '../../tests/testUtils'; -import { FAILED_TO_DISPATCH_EVENTS } from '../../exception_messages'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; @@ -639,7 +638,7 @@ describe('DefaultOdpEventManager', () => { const repeater = getMockRepeater(); const apiManager = getMockApiManager(); - apiManager.sendEvents.mockReturnValue(Promise.reject(new Error(FAILED_TO_DISPATCH_EVENTS))); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('FAILED_TO_DISPATCH_EVENTS'))); const backoffController = { backoff: vi.fn().mockReturnValue(666), @@ -741,7 +740,7 @@ describe('DefaultOdpEventManager', () => { const repeater = getMockRepeater(); const apiManager = getMockApiManager(); - apiManager.sendEvents.mockReturnValue(Promise.reject(new Error(FAILED_TO_DISPATCH_EVENTS))); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('FAILED_TO_DISPATCH_EVENTS'))); const backoffController = { backoff: vi.fn().mockReturnValue(666), diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 76aec79be..9bf107874 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -30,9 +30,10 @@ import { ODP_EVENT_MANAGER_IS_NOT_RUNNING, ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE, ODP_NOT_INTEGRATED, + FAILED_TO_DISPATCH_EVENTS_WITH_ARG, + ODP_EVENT_MANAGER_STOPPED } from '../../error_messages'; -import { sprintf } from '../../utils/fns'; -import { FAILED_TO_DISPATCH_EVENTS_WITH_ARG, ODP_EVENT_MANAGER_STOPPED } from '../../exception_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; export interface OdpEventManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; @@ -75,7 +76,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag private async executeDispatch(odpConfig: OdpConfig, batch: OdpEvent[]): Promise<unknown> { const res = await this.apiManager.sendEvents(odpConfig, batch); if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - return Promise.reject(new Error(sprintf(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode))); + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode)); } return await Promise.resolve(res); } @@ -153,7 +154,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } if (this.isNew()) { - this.startPromise.reject(new Error(ODP_EVENT_MANAGER_STOPPED)); + this.startPromise.reject(new OptimizelyError(ODP_EVENT_MANAGER_STOPPED)); } this.flush(); diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index 8ffc2721d..26c6e82e0 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -25,7 +25,6 @@ import { ODP_USER_KEY } from './constant'; import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; import { OdpEventManager } from './event_manager/odp_event_manager'; import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; -import { FAILED_TO_STOP } from '../exception_messages'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -694,7 +693,7 @@ describe('DefaultOdpManager', () => { await exhaustMicrotasks(); expect(odpManager.getState()).toEqual(ServiceState.Stopping); - eventManagerTerminatedPromise.reject(new Error(FAILED_TO_STOP)); + eventManagerTerminatedPromise.reject(new Error('FAILED_TO_STOP')); await expect(odpManager.onTerminated()).rejects.toThrow(); }); diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 3a7b4a62a..7b36c0eb9 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -29,7 +29,8 @@ import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; import { ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION, ODP_USER_KEY } from './constant'; import { isVuid } from '../vuid/vuid'; import { Maybe } from '../utils/type'; -import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from '../exception_messages'; +import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from '../error_messages'; +import { OptimizelyError } from '../error/optimizly_error'; export interface OdpManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean; @@ -137,7 +138,7 @@ export class DefaultOdpManager extends BaseService implements OdpManager { } if (!this.isRunning()) { - this.startPromise.reject(new Error(ODP_MANAGER_STOPPED_BEFORE_RUNNING)); + this.startPromise.reject(new OptimizelyError(ODP_MANAGER_STOPPED_BEFORE_RUNNING)); } this.state = ServiceState.Stopping; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index d1468bced..30d67cd72 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -36,27 +36,14 @@ import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; import { - AUDIENCE_EVALUATION_RESULT_COMBINED, - EXPERIMENT_NOT_RUNNING, - FEATURE_HAS_NO_EXPERIMENTS, FEATURE_NOT_ENABLED_FOR_USER, INVALID_CLIENT_ENGINE, INVALID_DEFAULT_DECIDE_OPTIONS, INVALID_OBJECT, NOT_ACTIVATING_USER, - RETURNING_STORED_VARIATION, - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - USER_FORCED_IN_VARIATION, - USER_HAS_FORCED_VARIATION, USER_HAS_NO_FORCED_VARIATION, USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - USER_HAS_NO_VARIATION, - USER_HAS_VARIATION, - USER_IN_ROLLOUT, USER_MAPPED_TO_FORCED_VARIATION, - USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_BUCKETED_INTO_TARGETING_RULE, - USER_NOT_IN_EXPERIMENT, USER_RECEIVED_DEFAULT_VARIABLE_VALUE, VALID_USER_PROFILE_SERVICE, VARIATION_REMOVED_FOR_USER, @@ -70,11 +57,28 @@ import { INVALID_INPUT_FORMAT, NO_VARIATION_FOR_EXPERIMENT_KEY, USER_NOT_IN_FORCED_VARIATION, - FORCED_BUCKETING_FAILED, + INSTANCE_CLOSED, + ONREADY_TIMEOUT_EXPIRED, } from '../error_messages'; -import { FAILED_TO_STOP, ONREADY_TIMEOUT_EXPIRED, PROMISE_SHOULD_NOT_HAVE_RESOLVED } from '../exception_messages'; + +import { + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_NOT_IN_EXPERIMENT, + FEATURE_HAS_NO_EXPERIMENTS, + USER_HAS_NO_VARIATION, + USER_HAS_VARIATION, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_IN_ROLLOUT, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + FORCED_BUCKETING_FAILED, + USER_HAS_FORCED_VARIATION, + USER_FORCED_IN_VARIATION, + RETURNING_STORED_VARIATION, + EXPERIMENT_NOT_RUNNING, +} from '../core/decision_service'; + import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP } from '../core/bucketer'; -import { error } from 'console'; var LOG_LEVEL = enums.LOG_LEVEL; var DECISION_SOURCES = enums.DECISION_SOURCES; @@ -5067,7 +5071,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'exp_with_audience') + sprintf(EXPERIMENT_NOT_RUNNING, 'exp_with_audience') ); }); @@ -5112,7 +5116,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstanceWithUserProfile.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(RETURNING_STORED_VARIATION, 'DECISION_SERVICE', variationKey2, experimentKey, userId) + sprintf(RETURNING_STORED_VARIATION, variationKey2, experimentKey, userId) ); }); @@ -5128,7 +5132,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', userId, variationKey) + sprintf(USER_FORCED_IN_VARIATION, userId, variationKey) ); }); @@ -5146,7 +5150,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', variationKey, experimentKey, userId) + sprintf(USER_HAS_FORCED_VARIATION, variationKey, experimentKey, userId) ); }); @@ -5163,7 +5167,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(FORCED_BUCKETING_FAILED, 'DECISION_SERVICE', variationKey, userId) + sprintf(FORCED_BUCKETING_FAILED, variationKey, userId) ); }); @@ -5176,7 +5180,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, userId, '1') ); }); @@ -5189,7 +5193,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'CA'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') + sprintf(USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, userId, '1') ); }); @@ -5202,7 +5206,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_IN_ROLLOUT, 'DECISION_SERVICE', userId, flagKey) + sprintf(USER_IN_ROLLOUT, userId, flagKey) ); }); @@ -5215,7 +5219,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'KO'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, 'Everyone Else') + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, userId, 'Everyone Else') ); }); @@ -5228,7 +5232,7 @@ describe('lib/optimizely', function() { user.setAttribute('browser', 'safari'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_NOT_BUCKETED_INTO_TARGETING_RULE, 'DECISION_SERVICE', userId, '2') + sprintf(USER_NOT_BUCKETED_INTO_TARGETING_RULE, userId, '2') ); }); @@ -5242,7 +5246,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_HAS_VARIATION, 'DECISION_SERVICE', userId, variationKey, experimentKey) + sprintf(USER_HAS_VARIATION, userId, variationKey, experimentKey) ); }); @@ -5260,7 +5264,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_HAS_NO_VARIATION, 'DECISION_SERVICE', userId, experimentKey) + sprintf(USER_HAS_NO_VARIATION, userId, experimentKey) ); }); @@ -5278,7 +5282,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'BUCKETER', userId, experimentKey, groupId) + sprintf(USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, userId, experimentKey, groupId) ); }); @@ -5293,7 +5297,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(FEATURE_HAS_NO_EXPERIMENTS, 'DECISION_SERVICE', flagKey) + sprintf(FEATURE_HAS_NO_EXPERIMENTS, flagKey) ); }); @@ -5306,7 +5310,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', userId, experimentKey) + sprintf(USER_NOT_IN_EXPERIMENT, userId, experimentKey) ); }); @@ -5326,7 +5330,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5351,7 +5354,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5376,7 +5378,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5401,7 +5402,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5426,7 +5426,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5451,7 +5450,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5476,7 +5474,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5500,7 +5497,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -9286,7 +9282,7 @@ describe('lib/optimizely', function() { describe('when the event processor onTerminated() method returns a promise that rejects', function() { beforeEach(function() { - eventProcessorStopPromise = Promise.reject(new Error(FAILED_TO_STOP)); + eventProcessorStopPromise = Promise.reject(new Error('FAILED_TO_STOP')); eventProcessorStopPromise.catch(() => {}); mockEventProcessor.onTerminated.returns(eventProcessorStopPromise); const mockConfigManager = getMockProjectConfigManager({ @@ -9317,10 +9313,11 @@ describe('lib/optimizely', function() { it('returns a promise that fulfills with an unsuccessful result object', function() { return optlyInstance.close().then(function(result) { - assert.deepEqual(result, { - success: false, - reason: 'Error: Failed to stop', - }); + // assert.deepEqual(result, { + // success: false, + // reason: 'Error: Failed to stop', + // }); + assert.isFalse(result.success); }); }); }); @@ -9467,9 +9464,10 @@ describe('lib/optimizely', function() { var readyPromise = optlyInstance.onReady({ timeout: 500 }); clock.tick(501); return readyPromise.then(() => { - return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); + return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); }, (err) => { - assert.equal(err.message, sprintf(ONREADY_TIMEOUT_EXPIRED, 500)); + assert.equal(err.baseMessage, ONREADY_TIMEOUT_EXPIRED); + assert.deepEqual(err.params, [ 500 ]); }); }); @@ -9493,7 +9491,8 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { - assert.equal(err.message, 'onReady timeout expired after 30000 ms') + assert.equal(err.baseMessage, ONREADY_TIMEOUT_EXPIRED); + assert.deepEqual(err.params, [ 30000 ]); }); }); @@ -9517,7 +9516,7 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { - assert.equal(err.message, 'Instance closed') + assert.equal(err.baseMessage, INSTANCE_CLOSED); }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 1d30e4fa1..87da57af7 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -52,7 +52,6 @@ import * as stringValidator from '../utils/string_value_validator'; import * as decision from '../core/decision'; import { - LOG_LEVEL, DECISION_SOURCES, DECISION_MESSAGES, FEATURE_VARIABLE_TYPES, @@ -78,6 +77,8 @@ import { EVENT_KEY_NOT_FOUND, NOT_TRACKING_USER, VARIABLE_REQUESTED_WITH_WRONG_TYPE, + ONREADY_TIMEOUT, + INSTANCE_CLOSED } from '../error_messages'; import { @@ -96,11 +97,10 @@ import { VALID_USER_PROFILE_SERVICE, VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, } from '../log_messages'; -import { INSTANCE_CLOSED } from '../exception_messages'; + import { ErrorNotifier } from '../error/error_notifier'; import { ErrorReporter } from '../error/error_reporter'; - -const MODULE_NAME = 'OPTIMIZELY'; +import { OptimizelyError } from '../error/optimizly_error'; const DEFAULT_ONREADY_TIMEOUT = 30000; @@ -375,7 +375,7 @@ export default class Optimizely implements Client { } if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'track'); + this.logger?.error(INVALID_OBJECT, 'track'); return; } @@ -549,14 +549,14 @@ export default class Optimizely implements Client { if (stringInputs.hasOwnProperty('user_id')) { const userId = stringInputs['user_id']; if (typeof userId !== 'string' || userId === null || userId === 'undefined') { - throw new Error(sprintf(INVALID_INPUT_FORMAT, MODULE_NAME, 'user_id')); + throw new OptimizelyError(INVALID_INPUT_FORMAT, 'user_id'); } delete stringInputs['user_id']; } Object.keys(stringInputs).forEach(key => { if (!stringValidator.validate(stringInputs[key as InputKey])) { - throw new Error(sprintf(INVALID_INPUT_FORMAT, MODULE_NAME, key)); + throw new OptimizelyError(INVALID_INPUT_FORMAT, key); } }); if (userAttributes) { @@ -1043,7 +1043,7 @@ export default class Optimizely implements Client { ): string | null { try { if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); + this.logger?.error(INVALID_OBJECT, 'getFeatureVariableString'); return null; } return this.getFeatureVariableForType( @@ -1335,14 +1335,12 @@ export default class Optimizely implements Client { const onReadyTimeout = () => { delete this.readyTimeouts[timeoutId]; - timeoutPromise.reject(new Error( - sprintf('onReady timeout expired after %s ms', timeoutValue) - )); + timeoutPromise.reject(new OptimizelyError(ONREADY_TIMEOUT, timeoutValue)); }; const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); const onClose = function() { - timeoutPromise.reject(new Error(INSTANCE_CLOSED)); + timeoutPromise.reject(new OptimizelyError(INSTANCE_CLOSED)); }; this.readyTimeouts[timeoutId] = { @@ -1573,7 +1571,7 @@ export default class Optimizely implements Client { if (!feature) { this.logger?.error(FEATURE_NOT_IN_DATAFILE, key); decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); - continue + continue; } validKeys.push(key); @@ -1625,7 +1623,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; if (!this.isValidInstance() || !configObj) { - this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'decideAll'); + this.logger?.error(INVALID_OBJECT, 'decideAll'); return decisionMap; } diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index 62b17cfe4..1f7c62473 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -23,14 +23,19 @@ import { RequestHandler, AbortableRequest, Headers, Response } from '../utils/ht import { Repeater } from '../utils/repeater/repeater'; import { Consumer, Fn } from '../utils/type'; import { isSuccessStatusCode } from '../utils/http_request_handler/http_util'; -import { DATAFILE_MANAGER_STOPPED, FAILED_TO_FETCH_DATAFILE } from '../exception_messages'; -import { DATAFILE_FETCH_REQUEST_FAILED, ERROR_FETCHING_DATAFILE } from '../error_messages'; +import { + DATAFILE_MANAGER_STOPPED, + DATAFILE_FETCH_REQUEST_FAILED, + ERROR_FETCHING_DATAFILE, + FAILED_TO_FETCH_DATAFILE, +} from '../error_messages'; import { ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN, MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS, RESPONSE_STATUS_CODE, SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, } from '../log_messages'; +import { OptimizelyError } from '../error/optimizly_error'; export class PollingDatafileManager extends BaseService implements DatafileManager { private requestHandler: RequestHandler; @@ -107,7 +112,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag } if (this.isNew() || this.isStarting()) { - this.startPromise.reject(new Error(DATAFILE_MANAGER_STOPPED)); + this.startPromise.reject(new OptimizelyError(DATAFILE_MANAGER_STOPPED)); } this.logger?.debug(DATAFILE_MANAGER_STOPPED); @@ -121,7 +126,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag private handleInitFailure(): void { this.state = ServiceState.Failed; this.repeater.stop(); - const error = new Error(FAILED_TO_FETCH_DATAFILE); + const error = new OptimizelyError(FAILED_TO_FETCH_DATAFILE); this.startPromise.reject(error); this.stopPromise.reject(error); } diff --git a/lib/project_config/project_config.tests.js b/lib/project_config/project_config.tests.js index e776ebf72..ff8e18624 100644 --- a/lib/project_config/project_config.tests.js +++ b/lib/project_config/project_config.tests.js @@ -312,9 +312,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experiment key in getExperimentId', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getExperimentId(configObj, 'invalidExperimentKey'); - }, sprintf(INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_KEY); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); it('should retrieve layer ID for valid experiment key in getLayerId', function() { @@ -322,9 +324,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experiment key in getLayerId', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getLayerId(configObj, 'invalidExperimentKey'); - }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { @@ -368,9 +372,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experiment key in getExperimentStatus', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getExperimentStatus(configObj, 'invalidExperimentKey'); - }, sprintf(INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_KEY); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); it('should return true if experiment status is set to Running in isActive', function() { @@ -404,9 +410,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experient key in getTrafficAllocation', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); - }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentId']); }); describe('#getVariationIdFromExperimentAndVariationKey', function() { @@ -686,9 +694,11 @@ describe('lib/core/project_config', function() { it('should throw error for invalid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); - }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentId']); }); it('should return experiment audienceIds if experiment has no audienceConditions', function() { diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 3671928ac..1a7ad4313 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { find, objectEntries, objectValues, sprintf, keyBy } from '../utils/fns'; +import { find, objectEntries, objectValues, keyBy } from '../utils/fns'; -import { LOG_LEVEL, FEATURE_VARIABLE_TYPES } from '../utils/enums'; +import { FEATURE_VARIABLE_TYPES } from '../utils/enums'; import configValidator from '../utils/config_validator'; import { LoggerFacade } from '../logging/logger'; @@ -50,6 +50,7 @@ import { VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, } from '../error_messages'; import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from '../log_messages'; +import { OptimizelyError } from '../error/optimizly_error'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type @@ -109,7 +110,6 @@ export interface ProjectConfig { const EXPERIMENT_RUNNING_STATUS = 'Running'; const RESERVED_ATTRIBUTE_PREFIX = '$opt_'; -const MODULE_NAME = 'PROJECT_CONFIG'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { @@ -213,7 +213,7 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.integrations.forEach(integration => { if (!('key' in integration)) { - throw new Error(sprintf(MISSING_INTEGRATION_KEY, MODULE_NAME)); + throw new OptimizelyError(MISSING_INTEGRATION_KEY); } if (integration.key === 'odp') { @@ -361,7 +361,7 @@ function isLogicalOperator(condition: string): boolean { export const getExperimentId = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new OptimizelyError(INVALID_EXPERIMENT_KEY, experimentKey); } return experiment.id; }; @@ -376,7 +376,7 @@ export const getExperimentId = function(projectConfig: ProjectConfig, experiment export const getLayerId = function(projectConfig: ProjectConfig, experimentId: string): string { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.layerId; }; @@ -436,7 +436,7 @@ export const getEventId = function(projectConfig: ProjectConfig, eventKey: strin export const getExperimentStatus = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new OptimizelyError(INVALID_EXPERIMENT_KEY, experimentKey); } return experiment.status; }; @@ -478,7 +478,7 @@ export const getExperimentAudienceConditions = function( ): Array<string | string[]> { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.audienceConditions || experiment.audienceIds; @@ -547,7 +547,7 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper } } - throw new Error(sprintf(EXPERIMENT_KEY_NOT_IN_DATAFILE, MODULE_NAME, experimentKey)); + throw new OptimizelyError(EXPERIMENT_KEY_NOT_IN_DATAFILE, experimentKey); }; /** @@ -560,7 +560,7 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper export const getTrafficAllocation = function(projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.trafficAllocation; }; @@ -836,7 +836,7 @@ export const tryCreatingProjectConfig = function( config.jsonSchemaValidator(newDatafileObj); config.logger?.info(VALID_DATAFILE); } else { - config.logger?.info(SKIPPING_JSON_VALIDATION, MODULE_NAME); + config.logger?.info(SKIPPING_JSON_VALIDATION); } const createProjectConfigArgs = [newDatafileObj]; diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index 4a851347e..985a0524e 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -22,11 +22,8 @@ import { scheduleMicrotask } from '../utils/microtask'; import { Service, ServiceState, BaseService } from '../service'; import { Consumer, Fn, Transformer } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; -import { - DATAFILE_MANAGER_FAILED_TO_START, - DATAFILE_MANAGER_STOPPED, - YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE, -} from '../exception_messages'; +import { DATAFILE_MANAGER_STOPPED, NO_SDKKEY_OR_DATAFILE, DATAFILE_MANAGER_FAILED_TO_START } from '../error_messages'; +import { OptimizelyError } from '../error/optimizly_error'; interface ProjectConfigManagerConfig { datafile?: string | Record<string, unknown>; @@ -73,7 +70,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf this.state = ServiceState.Starting; if (!this.datafile && !this.datafileManager) { - this.handleInitError(new Error(YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE)); + this.handleInitError(new OptimizelyError(NO_SDKKEY_OR_DATAFILE)); return; } @@ -197,7 +194,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf } if (this.isNew() || this.isStarting()) { - this.startPromise.reject(new Error(DATAFILE_MANAGER_STOPPED)); + this.startPromise.reject(new OptimizelyError(DATAFILE_MANAGER_STOPPED)); } this.state = ServiceState.Stopping; diff --git a/lib/utils/attributes_validator/index.tests.js b/lib/utils/attributes_validator/index.tests.js index ed79d9470..17daf68e8 100644 --- a/lib/utils/attributes_validator/index.tests.js +++ b/lib/utils/attributes_validator/index.tests.js @@ -28,24 +28,27 @@ describe('lib/utils/attributes_validator', function() { it('should throw an error if attributes is an array', function() { var attributesArray = ['notGonnaWork']; - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(attributesArray); - }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes is null', function() { - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(null); - }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes is a function', function() { function invalidInput() { console.log('This is an invalid input!'); } - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(invalidInput); - }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes contains a key with an undefined value', function() { @@ -53,9 +56,11 @@ describe('lib/utils/attributes_validator', function() { var attributes = {}; attributes[attributeKey] = undefined; - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(attributes); - }, sprintf(UNDEFINED_ATTRIBUTE, 'ATTRIBUTES_VALIDATOR', attributeKey)); + }); + assert.equal(ex.baseMessage, UNDEFINED_ATTRIBUTE); + assert.deepEqual(ex.params, [attributeKey]); }); }); diff --git a/lib/utils/attributes_validator/index.ts b/lib/utils/attributes_validator/index.ts index 255b99419..adbe70bdb 100644 --- a/lib/utils/attributes_validator/index.ts +++ b/lib/utils/attributes_validator/index.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import fns from '../../utils/fns'; import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from '../../error_messages'; - -const MODULE_NAME = 'ATTRIBUTES_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validates user's provided attributes @@ -32,12 +30,12 @@ export function validate(attributes: unknown): boolean { if (typeof attributes === 'object' && !Array.isArray(attributes) && attributes !== null) { Object.keys(attributes).forEach(function(key) { if (typeof (attributes as ObjectWithUnknownProperties)[key] === 'undefined') { - throw new Error(sprintf(UNDEFINED_ATTRIBUTE, MODULE_NAME, key)); + throw new OptimizelyError(UNDEFINED_ATTRIBUTE, key); } }); return true; } else { - throw new Error(sprintf(INVALID_ATTRIBUTES, MODULE_NAME)); + throw new OptimizelyError(INVALID_ATTRIBUTES); } } diff --git a/lib/utils/config_validator/index.tests.js b/lib/utils/config_validator/index.tests.js index b7a8711c7..8ff6e7581 100644 --- a/lib/utils/config_validator/index.tests.js +++ b/lib/utils/config_validator/index.tests.js @@ -31,45 +31,52 @@ describe('lib/utils/config_validator', function() { describe('APIs', function() { describe('validate', function() { it('should complain if the provided error handler is invalid', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validate({ errorHandler: {}, }); - }, sprintf(INVALID_ERROR_HANDLER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ERROR_HANDLER); }); it('should complain if the provided event dispatcher is invalid', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validate({ eventDispatcher: {}, }); - }, sprintf(INVALID_EVENT_DISPATCHER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_DISPATCHER); }); it('should complain if the provided logger is invalid', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validate({ logger: {}, }); - }, sprintf(INVALID_LOGGER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_LOGGER); }); it('should complain if datafile is not provided', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile(); - }, sprintf(NO_DATAFILE_SPECIFIED, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, NO_DATAFILE_SPECIFIED); }); it('should complain if datafile is malformed', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile('abc'); - }, sprintf(INVALID_DATAFILE_MALFORMED, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_DATAFILE_MALFORMED); }); it('should complain if datafile version is not supported', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())); - }, sprintf(INVALID_DATAFILE_VERSION, 'CONFIG_VALIDATOR', '5')); + }); + assert.equal(ex.baseMessage, INVALID_DATAFILE_VERSION); + assert.deepEqual(ex.params, ['5']); }); it('should not complain if datafile is valid', function() { diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index f3c2eadfd..a61d4f1cf 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import { @@ -28,8 +27,8 @@ import { INVALID_LOGGER, NO_DATAFILE_SPECIFIED, } from '../../error_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; -const MODULE_NAME = 'CONFIG_VALIDATOR'; const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE_VERSIONS.V4]; /** @@ -48,17 +47,17 @@ export const validate = function(config: unknown): boolean { const eventDispatcher = configObj['eventDispatcher']; const logger = configObj['logger']; if (errorHandler && typeof (errorHandler as ObjectWithUnknownProperties)['handleError'] !== 'function') { - throw new Error(sprintf(INVALID_ERROR_HANDLER, MODULE_NAME)); + throw new OptimizelyError(INVALID_ERROR_HANDLER); } if (eventDispatcher && typeof (eventDispatcher as ObjectWithUnknownProperties)['dispatchEvent'] !== 'function') { - throw new Error(sprintf(INVALID_EVENT_DISPATCHER, MODULE_NAME)); + throw new OptimizelyError(INVALID_EVENT_DISPATCHER); } if (logger && typeof (logger as ObjectWithUnknownProperties)['info'] !== 'function') { - throw new Error(sprintf(INVALID_LOGGER, MODULE_NAME)); + throw new OptimizelyError(INVALID_LOGGER); } return true; } - throw new Error(sprintf(INVALID_CONFIG, MODULE_NAME)); + throw new OptimizelyError(INVALID_CONFIG); } /** @@ -73,19 +72,19 @@ export const validate = function(config: unknown): boolean { // eslint-disable-next-line export const validateDatafile = function(datafile: unknown): any { if (!datafile) { - throw new Error(sprintf(NO_DATAFILE_SPECIFIED, MODULE_NAME)); + throw new OptimizelyError(NO_DATAFILE_SPECIFIED); } if (typeof datafile === 'string') { // Attempt to parse the datafile string try { datafile = JSON.parse(datafile); } catch (ex) { - throw new Error(sprintf(INVALID_DATAFILE_MALFORMED, MODULE_NAME)); + throw new OptimizelyError(INVALID_DATAFILE_MALFORMED); } } if (typeof datafile === 'object' && !Array.isArray(datafile) && datafile !== null) { if (SUPPORTED_VERSIONS.indexOf(datafile['version' as keyof unknown]) === -1) { - throw new Error(sprintf(INVALID_DATAFILE_VERSION, MODULE_NAME, datafile['version' as keyof unknown])); + throw new OptimizelyError(INVALID_DATAFILE_VERSION, datafile['version' as keyof unknown]); } } diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index c8fc9835f..8819086a9 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -23,14 +23,12 @@ import { EventTags } from '../../event_processor/event_builder/user_event'; import { LoggerFacade } from '../../logging/logger'; import { - LOG_LEVEL, RESERVED_EVENT_KEYWORDS, } from '../enums'; /** * Provides utility method for parsing event tag values */ -const MODULE_NAME = 'EVENT_TAG_UTILS'; const REVENUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.REVENUE; const VALUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.VALUE; diff --git a/lib/utils/event_tags_validator/index.tests.js b/lib/utils/event_tags_validator/index.tests.js index fcf8d4bd3..a7ea58956 100644 --- a/lib/utils/event_tags_validator/index.tests.js +++ b/lib/utils/event_tags_validator/index.tests.js @@ -28,24 +28,27 @@ describe('lib/utils/event_tags_validator', function() { it('should throw an error if event tags is an array', function() { var eventTagsArray = ['notGonnaWork']; - assert.throws(function() { + const ex = assert.throws(function() { validate(eventTagsArray); - }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); it('should throw an error if event tags is null', function() { - assert.throws(function() { + const ex = assert.throws(function() { validate(null); - }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }) + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); it('should throw an error if event tags is a function', function() { function invalidInput() { console.log('This is an invalid input!'); } - assert.throws(function() { + const ex = assert.throws(function() { validate(invalidInput); - }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); }); }); diff --git a/lib/utils/event_tags_validator/index.ts b/lib/utils/event_tags_validator/index.ts index d898cc202..6dde8a045 100644 --- a/lib/utils/event_tags_validator/index.ts +++ b/lib/utils/event_tags_validator/index.ts @@ -17,10 +17,8 @@ /** * Provides utility method for validating that event tags user has provided are valid */ +import { OptimizelyError } from '../../error/optimizly_error'; import { INVALID_EVENT_TAGS } from '../../error_messages'; -import { sprintf } from '../../utils/fns'; - -const MODULE_NAME = 'EVENT_TAGS_VALIDATOR'; /** * Validates user's provided event tags @@ -32,6 +30,6 @@ export function validate(eventTags: unknown): boolean { if (typeof eventTags === 'object' && !Array.isArray(eventTags) && eventTags !== null) { return true; } else { - throw new Error(sprintf(INVALID_EVENT_TAGS, MODULE_NAME)); + throw new OptimizelyError(INVALID_EVENT_TAGS); } } diff --git a/lib/utils/executor/backoff_retry_runner.ts b/lib/utils/executor/backoff_retry_runner.ts index f939f9cc6..93b3ef748 100644 --- a/lib/utils/executor/backoff_retry_runner.ts +++ b/lib/utils/executor/backoff_retry_runner.ts @@ -1,4 +1,5 @@ -import { RETRY_CANCELLED } from "../../exception_messages"; +import { OptimizelyError } from "../../error/optimizly_error"; +import { RETRY_CANCELLED } from "../../error_messages"; import { resolvablePromise, ResolvablePromise } from "../promise/resolvablePromise"; import { BackoffController } from "../repeater/repeater"; import { AsyncProducer, Fn } from "../type"; @@ -27,7 +28,7 @@ const runTask = <T>( return; } if (cancelSignal.cancelled) { - returnPromise.reject(new Error(RETRY_CANCELLED)); + returnPromise.reject(new OptimizelyError(RETRY_CANCELLED)); return; } const delay = backoff?.backoff() ?? 0; diff --git a/lib/utils/http_request_handler/request_handler.browser.ts b/lib/utils/http_request_handler/request_handler.browser.ts index 88157e6a9..5ab8ce1cb 100644 --- a/lib/utils/http_request_handler/request_handler.browser.ts +++ b/lib/utils/http_request_handler/request_handler.browser.ts @@ -19,6 +19,7 @@ import { LoggerFacade, LogLevel } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; import { REQUEST_ERROR, REQUEST_TIMEOUT } from '../../error_messages'; import { UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from '../../log_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Handles sending requests and receiving responses over HTTP via XMLHttpRequest @@ -52,7 +53,7 @@ export class BrowserRequestHandler implements RequestHandler { if (request.readyState === XMLHttpRequest.DONE) { const statusCode = request.status; if (statusCode === 0) { - reject(new Error(REQUEST_ERROR)); + reject(new OptimizelyError(REQUEST_ERROR)); return; } diff --git a/lib/utils/http_request_handler/request_handler.node.ts b/lib/utils/http_request_handler/request_handler.node.ts index 6626510e8..cf0a620db 100644 --- a/lib/utils/http_request_handler/request_handler.node.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -20,8 +20,8 @@ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import decompressResponse from 'decompress-response'; import { LoggerFacade } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; -import { sprintf } from '../fns'; import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from '../../error_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Handles sending requests and receiving responses over HTTP via NodeJS http module @@ -48,7 +48,7 @@ export class NodeRequestHandler implements RequestHandler { if (parsedUrl.protocol !== 'https:') { return { - responsePromise: Promise.reject(new Error(sprintf(UNSUPPORTED_PROTOCOL, parsedUrl.protocol))), + responsePromise: Promise.reject(new OptimizelyError(UNSUPPORTED_PROTOCOL, parsedUrl.protocol)), abort: () => {}, }; } @@ -130,7 +130,7 @@ export class NodeRequestHandler implements RequestHandler { request.on('timeout', () => { aborted = true; request.destroy(); - reject(new Error(REQUEST_TIMEOUT)); + reject(new OptimizelyError(REQUEST_TIMEOUT)); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -140,7 +140,7 @@ export class NodeRequestHandler implements RequestHandler { } else if (typeof err === 'string') { reject(new Error(err)); } else { - reject(new Error(REQUEST_ERROR)); + reject(new OptimizelyError(REQUEST_ERROR)); } }); @@ -166,7 +166,7 @@ export class NodeRequestHandler implements RequestHandler { } if (!incomingMessage.statusCode) { - reject(new Error(NO_STATUS_CODE_IN_RESPONSE)); + reject(new OptimizelyError(NO_STATUS_CODE_IN_RESPONSE)); return; } diff --git a/lib/utils/json_schema_validator/index.tests.js b/lib/utils/json_schema_validator/index.tests.js index d54bc39a4..aaee8dc27 100644 --- a/lib/utils/json_schema_validator/index.tests.js +++ b/lib/utils/json_schema_validator/index.tests.js @@ -31,9 +31,10 @@ describe('lib/utils/json_schema_validator', function() { }); it('should throw an error if no json object is passed in', function() { - assert.throws(function() { + const ex = assert.throws(function() { validate(); - }, sprintf(NO_JSON_PROVIDED, 'JSON_SCHEMA_VALIDATOR (Project Config JSON Schema)')); + }); + assert.equal(ex.baseMessage, NO_JSON_PROVIDED); }); it('should validate specified Optimizely datafile', function() { diff --git a/lib/utils/json_schema_validator/index.ts b/lib/utils/json_schema_validator/index.ts index a4bac5674..f5824931c 100644 --- a/lib/utils/json_schema_validator/index.ts +++ b/lib/utils/json_schema_validator/index.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../fns'; import { JSONSchema4, validate as jsonSchemaValidator } from 'json-schema'; import schema from '../../project_config/project_config_schema'; import { INVALID_DATAFILE, INVALID_JSON, NO_JSON_PROVIDED } from '../../error_messages'; - -const MODULE_NAME = 'JSON_SCHEMA_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validate the given json object against the specified schema @@ -33,10 +31,8 @@ export function validate( validationSchema: JSONSchema4 = schema, shouldThrowOnError = true ): boolean { - const moduleTitle = `${MODULE_NAME} (${validationSchema.title})`; - if (typeof jsonObject !== 'object' || jsonObject === null) { - throw new Error(sprintf(NO_JSON_PROVIDED, moduleTitle)); + throw new OptimizelyError(NO_JSON_PROVIDED); } const result = jsonSchemaValidator(jsonObject, validationSchema); @@ -49,10 +45,10 @@ export function validate( } if (Array.isArray(result.errors)) { - throw new Error( - sprintf(INVALID_DATAFILE, moduleTitle, result.errors[0].property, result.errors[0].message) + throw new OptimizelyError( + INVALID_DATAFILE, result.errors[0].property, result.errors[0].message ); } - throw new Error(sprintf(INVALID_JSON, moduleTitle)); + throw new OptimizelyError(INVALID_JSON); } diff --git a/lib/utils/semantic_version/index.ts b/lib/utils/semantic_version/index.ts index ecd7bb804..2e5e02e47 100644 --- a/lib/utils/semantic_version/index.ts +++ b/lib/utils/semantic_version/index.ts @@ -17,8 +17,6 @@ import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; import { LoggerFacade } from '../../logging/logger'; import { VERSION_TYPE } from '../enums'; -const MODULE_NAME = 'SEMANTIC VERSION'; - /** * Evaluate if provided string is number only * @param {unknown} content diff --git a/lib/utils/type.ts b/lib/utils/type.ts index ddf3871aa..0ddc6fc3c 100644 --- a/lib/utils/type.ts +++ b/lib/utils/type.ts @@ -26,3 +26,5 @@ export type Producer<T> = () => T; export type AsyncProducer<T> = () => Promise<T>; export type Maybe<T> = T | undefined; + +export type Either<A, B> = { type: 'left', value: A } | { type: 'right', value: B }; diff --git a/lib/utils/user_profile_service_validator/index.tests.js b/lib/utils/user_profile_service_validator/index.tests.js index f12f790ea..8f2e50b28 100644 --- a/lib/utils/user_profile_service_validator/index.tests.js +++ b/lib/utils/user_profile_service_validator/index.tests.js @@ -26,13 +26,11 @@ describe('lib/utils/user_profile_service_validator', function() { var missingLookupFunction = { save: function() {}, }; - assert.throws(function() { + const ex = assert.throws(function() { validate(missingLookupFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'lookup'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'lookup'"]); }); it("should throw if 'lookup' is not a function", function() { @@ -40,26 +38,27 @@ describe('lib/utils/user_profile_service_validator', function() { save: function() {}, lookup: 'notGonnaWork', }; - assert.throws(function() { + const ex = assert.throws(function() { validate(lookupNotFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'lookup'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'lookup'"]); }); it("should throw if the instance does not provide a 'save' function", function() { var missingSaveFunction = { lookup: function() {}, }; - assert.throws(function() { + const ex = assert.throws(function() { validate(missingSaveFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'save'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'save'"]); + // , sprintf( + // INVALID_USER_PROFILE_SERVICE, + // 'USER_PROFILE_SERVICE_VALIDATOR', + // "Missing function 'save'" + // )); }); it("should throw if 'save' is not a function", function() { @@ -67,13 +66,11 @@ describe('lib/utils/user_profile_service_validator', function() { lookup: function() {}, save: 'notGonnaWork', }; - assert.throws(function() { + const ex = assert.throws(function() { validate(saveNotFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'save'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'save'"]); }); it('should return true if the instance is valid', function() { diff --git a/lib/utils/user_profile_service_validator/index.ts b/lib/utils/user_profile_service_validator/index.ts index 8f51fc137..cb7529dcb 100644 --- a/lib/utils/user_profile_service_validator/index.ts +++ b/lib/utils/user_profile_service_validator/index.ts @@ -18,11 +18,10 @@ * Provides utility method for validating that the given user profile service implementation is valid. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import { INVALID_USER_PROFILE_SERVICE } from '../../error_messages'; -const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validates user's provided user profile service instance @@ -34,11 +33,11 @@ const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; export function validate(userProfileServiceInstance: unknown): boolean { if (typeof userProfileServiceInstance === 'object' && userProfileServiceInstance !== null) { if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['lookup'] !== 'function') { - throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'lookup'")); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, "Missing function 'lookup'"); } else if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['save'] !== 'function') { - throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'save'")); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, "Missing function 'save'"); } return true; } - throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME)); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, 'Not an object'); } diff --git a/lib/vuid/vuid_manager_factory.node.spec.ts b/lib/vuid/vuid_manager_factory.node.spec.ts index 048704794..0d8a6af5b 100644 --- a/lib/vuid/vuid_manager_factory.node.spec.ts +++ b/lib/vuid/vuid_manager_factory.node.spec.ts @@ -17,7 +17,7 @@ import { vi, describe, expect, it } from 'vitest'; import { createVuidManager } from './vuid_manager_factory.node'; -import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from '../exception_messages'; +import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from './vuid_manager_factory.node'; describe('createVuidManager', () => { it('should throw an error', () => { diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts index 6d194ce0b..ebc7fd373 100644 --- a/lib/vuid/vuid_manager_factory.node.ts +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from '../exception_messages'; import { VuidManager } from './vuid_manager'; import { VuidManagerOptions } from './vuid_manager_factory'; +export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; + export const createVuidManager = (options: VuidManagerOptions): VuidManager => { throw new Error(VUID_IS_NOT_SUPPORTED_IN_NODEJS); }; From 9adc544c94dcec7264be595d54f6a2fed415a223 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 23 Jan 2025 20:07:33 +0600 Subject: [PATCH 114/200] [FSSDK-11090] use path alias for log message imports (#987) --- lib/core/audience_evaluator/index.tests.js | 2 +- lib/core/audience_evaluator/index.ts | 4 ++-- .../index.tests.js | 2 +- .../odp_segment_condition_evaluator/index.ts | 2 +- lib/core/bucketer/index.tests.js | 2 +- lib/core/bucketer/index.ts | 2 +- .../index.tests.js | 7 ++---- .../index.ts | 4 ++-- lib/core/decision_service/index.tests.js | 4 ++-- lib/core/decision_service/index.ts | 4 ++-- lib/event_processor/batch_event_processor.ts | 2 +- .../event_dispatcher/default_dispatcher.ts | 2 +- .../send_beacon_dispatcher.browser.ts | 2 +- .../forwarding_event_processor.ts | 2 +- lib/index.browser.ts | 2 +- lib/index.node.ts | 2 -- .../error_message.ts} | 2 +- .../log_message.ts} | 1 - lib/message/message_resolver.ts | 4 ++-- lib/notification_center/index.ts | 2 +- lib/odp/event_manager/odp_event_manager.ts | 2 +- lib/odp/odp_manager.ts | 2 +- .../segment_manager/odp_segment_manager.ts | 2 +- lib/optimizely/index.tests.js | 4 ++-- lib/optimizely/index.ts | 4 ++-- lib/optimizely_user_context/index.tests.js | 2 +- .../polling_datafile_manager.ts | 4 ++-- lib/project_config/project_config.tests.js | 2 +- lib/project_config/project_config.ts | 4 ++-- lib/project_config/project_config_manager.ts | 2 +- lib/utils/attributes_validator/index.tests.js | 2 +- lib/utils/attributes_validator/index.ts | 2 +- lib/utils/config_validator/index.tests.js | 2 +- lib/utils/config_validator/index.ts | 2 +- lib/utils/event_tag_utils/index.tests.js | 2 +- lib/utils/event_tag_utils/index.ts | 2 +- lib/utils/event_tags_validator/index.tests.js | 3 +-- lib/utils/event_tags_validator/index.ts | 2 +- lib/utils/executor/backoff_retry_runner.ts | 2 +- .../request_handler.browser.ts | 4 ++-- .../request_handler.node.ts | 2 +- .../json_schema_validator/index.tests.js | 3 +-- lib/utils/json_schema_validator/index.ts | 2 +- lib/utils/semantic_version/index.ts | 2 +- .../index.tests.js | 3 +-- .../user_profile_service_validator/index.ts | 2 +- package-lock.json | 24 +++++++++++++++++++ package.json | 9 +++---- rollup.config.js | 15 ++++++++++++ tsconfig.json | 6 +++++ vitest.config.mts | 8 ++++++- 51 files changed, 112 insertions(+), 69 deletions(-) rename lib/{error_messages.ts => message/error_message.ts} (99%) rename lib/{log_messages.ts => message/log_message.ts} (98%) diff --git a/lib/core/audience_evaluator/index.tests.js b/lib/core/audience_evaluator/index.tests.js index bc725a428..1dc5efd30 100644 --- a/lib/core/audience_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/index.tests.js @@ -20,7 +20,7 @@ import { sprintf } from '../../utils/fns'; import AudienceEvaluator, { createAudienceEvaluator } from './index'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; -import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from '../../log_messages'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from 'log_message'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var mockLogger = { diff --git a/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts index 4ada47bbe..e2b3bce0a 100644 --- a/lib/core/audience_evaluator/index.ts +++ b/lib/core/audience_evaluator/index.ts @@ -17,8 +17,8 @@ import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; import * as odpSegmentsConditionEvaluator from './odp_segment_condition_evaluator'; import { Audience, Condition, OptimizelyUserContext } from '../../shared_types'; -import { CONDITION_EVALUATOR_ERROR, UNKNOWN_CONDITION_TYPE } from '../../error_messages'; -import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE} from '../../log_messages'; +import { CONDITION_EVALUATOR_ERROR, UNKNOWN_CONDITION_TYPE } from 'error_message'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE} from 'log_message'; import { LoggerFacade } from '../../logging/logger'; export class AudienceEvaluator { diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js index 684e28258..cc9218887 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js @@ -19,7 +19,7 @@ import { sprintf } from '../../../utils/fns'; import { LOG_LEVEL } from '../../../utils/enums'; import * as odpSegmentEvalutor from './'; -import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; +import { UNKNOWN_MATCH_TYPE } from 'error_message'; var odpSegment1Condition = { "value": "odp-segment-1", diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts index d97ee9db5..7380c9269 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; +import { UNKNOWN_MATCH_TYPE } from 'error_message'; import { LoggerFacade } from '../../../logging/logger'; import { Condition, OptimizelyUserContext } from '../../../shared_types'; diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index 12bea0d3a..023431af7 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -22,7 +22,7 @@ import * as bucketer from './'; import { LOG_LEVEL } from '../../utils/enums'; import projectConfig from '../../project_config/project_config'; import { getTestProjectConfig } from '../../tests/test_data'; -import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; +import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from 'error_message'; import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index 6d23856e5..d965e0217 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -26,7 +26,7 @@ import { Group, } from '../../shared_types'; -import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; +import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.'; diff --git a/lib/core/custom_attribute_condition_evaluator/index.tests.js b/lib/core/custom_attribute_condition_evaluator/index.tests.js index b17f3d3f7..12607e001 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.tests.js +++ b/lib/core/custom_attribute_condition_evaluator/index.tests.js @@ -17,20 +17,17 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; -import { - LOG_LEVEL, -} from '../../utils/enums'; import * as customAttributeEvaluator from './'; import { MISSING_ATTRIBUTE_VALUE, UNEXPECTED_TYPE_NULL, -} from '../../log_messages'; +} from 'log_message'; import { UNKNOWN_MATCH_TYPE, UNEXPECTED_TYPE, OUT_OF_BOUNDS, UNEXPECTED_CONDITION_VALUE, -} from '../../error_messages'; +} from 'error_message'; var browserConditionSafari = { name: 'browser_type', diff --git a/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts index c722c1837..797a7d4e0 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/lib/core/custom_attribute_condition_evaluator/index.ts @@ -20,13 +20,13 @@ import { compareVersion } from '../../utils/semantic_version'; import { MISSING_ATTRIBUTE_VALUE, UNEXPECTED_TYPE_NULL, -} from '../../log_messages'; +} from 'log_message'; import { OUT_OF_BOUNDS, UNEXPECTED_TYPE, UNEXPECTED_CONDITION_VALUE, UNKNOWN_MATCH_TYPE -} from '../../error_messages'; +} from 'error_message'; import { LoggerFacade } from '../../logging/logger'; const EXACT_MATCH_TYPE = 'exact'; diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 470d998eb..89b7113eb 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -45,7 +45,7 @@ import { VALID_BUCKETING_ID, SAVED_USER_VARIATION, SAVED_VARIATION_NOT_FOUND, -} from '../../log_messages'; +} from 'log_message'; import { EXPERIMENT_NOT_RUNNING, @@ -64,7 +64,7 @@ import { USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, } from '../decision_service/index'; -import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from '../../error_messages'; +import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from 'error_message'; var testData = getTestProjectConfig(); var testDataWithFeatures = getTestProjectConfigWithFeatures(); diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 9867d9b19..beb6b24da 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -56,7 +56,7 @@ import { USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR, BUCKETING_ID_NOT_STRING, -} from '../../error_messages'; +} from 'error_message'; import { SAVED_USER_VARIATION, @@ -70,7 +70,7 @@ import { USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, VALID_BUCKETING_ID, VARIATION_REMOVED_FOR_USER, -} from '../../log_messages'; +} from 'log_message'; import { OptimizelyError } from '../../error/optimizly_error'; export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index dae605d88..97b4dd8f4 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -27,7 +27,7 @@ import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; import { EventEmitter } from "../utils/event_emitter/event_emitter"; import { IdGenerator } from "../utils/id_generator"; import { areEventContextsEqual } from "./event_builder/user_event"; -import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "../error_messages"; +import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "error_message"; import { OptimizelyError } from "../error/optimizly_error"; export const DEFAULT_MIN_BACKOFF = 1000; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.ts b/lib/event_processor/event_dispatcher/default_dispatcher.ts index a812541cd..30da34823 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { OptimizelyError } from '../../error/optimizly_error'; -import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from '../../error_messages'; +import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from 'error_message'; import { RequestHandler } from '../../utils/http_request_handler/http'; import { EventDispatcher, EventDispatcherResponse, LogEvent } from './event_dispatcher'; diff --git a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts index d3130342a..006adedd6 100644 --- a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts +++ b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts @@ -15,7 +15,7 @@ */ import { OptimizelyError } from '../../error/optimizly_error'; -import { SEND_BEACON_FAILED } from '../../error_messages'; +import { SEND_BEACON_FAILED } from 'error_message'; import { EventDispatcher, EventDispatcherResponse } from './event_dispatcher'; export type Event = { diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 8ac6f6631..d516afe7c 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -23,7 +23,7 @@ import { buildLogEvent } from './event_builder/log_event'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; -import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from '../error_messages'; +import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from 'error_message'; import { OptimizelyError } from '../error/optimizly_error'; class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; diff --git a/lib/index.browser.ts b/lib/index.browser.ts index c25971393..48c996cbd 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -29,7 +29,7 @@ import { createPollingProjectConfigManager } from './project_config/config_manag import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.browser'; import { createVuidManager } from './vuid/vuid_manager_factory.browser'; import { createOdpManager } from './odp/odp_manager_factory.browser'; -import { ODP_DISABLED, UNABLE_TO_ATTACH_UNLOAD } from './log_messages'; +import { UNABLE_TO_ATTACH_UNLOAD } from 'error_message'; import { extractLogger, createLogger } from './logging/logger_factory'; import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; import { LoggerFacade } from './logging/logger'; diff --git a/lib/index.node.ts b/lib/index.node.ts index ba31fcbee..f66abcf28 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -27,8 +27,6 @@ import { createPollingProjectConfigManager } from './project_config/config_manag import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; import { createVuidManager } from './vuid/vuid_manager_factory.node'; import { createOdpManager } from './odp/odp_manager_factory.node'; -import { ODP_DISABLED } from './log_messages'; -import { create } from 'domain'; import { extractLogger, createLogger } from './logging/logger_factory'; import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; import { Maybe } from './utils/type'; diff --git a/lib/error_messages.ts b/lib/message/error_message.ts similarity index 99% rename from lib/error_messages.ts rename to lib/message/error_message.ts index 7ef14178d..97844f639 100644 --- a/lib/error_messages.ts +++ b/lib/message/error_message.ts @@ -142,6 +142,6 @@ export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before ru export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; export const ONREADY_TIMEOUT_EXPIRED = 'onReady timeout expired after %s ms'; export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; - +export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to page unload event: "%s"'; export const messages: string[] = []; diff --git a/lib/log_messages.ts b/lib/message/log_message.ts similarity index 98% rename from lib/log_messages.ts rename to lib/message/log_message.ts index 6123adc74..bbd1d110e 100644 --- a/lib/log_messages.ts +++ b/lib/message/log_message.ts @@ -75,7 +75,6 @@ export const MISSING_ATTRIBUTE_VALUE = export const UNEXPECTED_TYPE_NULL = 'Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".'; export const UPDATED_OPTIMIZELY_CONFIG = 'Updated Optimizely config to revision %s (project id %s)'; -export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to page unload event: "%s"'; export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; export const ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN = 'Adding Authorization header with Bearer Token'; export const MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS = 'Making datafile request to url %s with headers: %s'; diff --git a/lib/message/message_resolver.ts b/lib/message/message_resolver.ts index 4f6d38752..07a0cefdf 100644 --- a/lib/message/message_resolver.ts +++ b/lib/message/message_resolver.ts @@ -1,5 +1,5 @@ -import { messages as infoMessages } from '../log_messages'; -import { messages as errorMessages } from '../error_messages'; +import { messages as infoMessages } from 'log_message'; +import { messages as errorMessages } from 'error_message'; export interface MessageResolver { resolve(baseMessage: string): string; diff --git a/lib/notification_center/index.ts b/lib/notification_center/index.ts index 2db4d36d7..7b17ba658 100644 --- a/lib/notification_center/index.ts +++ b/lib/notification_center/index.ts @@ -20,7 +20,7 @@ import { NOTIFICATION_TYPES } from './type'; import { NotificationType, NotificationPayload } from './type'; import { Consumer, Fn } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; -import { NOTIFICATION_LISTENER_EXCEPTION } from '../error_messages'; +import { NOTIFICATION_LISTENER_EXCEPTION } from 'error_message'; import { ErrorReporter } from '../error/error_reporter'; import { ErrorNotifier } from '../error/error_notifier'; diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 9bf107874..11d4b37f1 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -32,7 +32,7 @@ import { ODP_NOT_INTEGRATED, FAILED_TO_DISPATCH_EVENTS_WITH_ARG, ODP_EVENT_MANAGER_STOPPED -} from '../../error_messages'; +} from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; export interface OdpEventManager extends Service { diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 7b36c0eb9..6e7da8769 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -29,7 +29,7 @@ import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; import { ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION, ODP_USER_KEY } from './constant'; import { isVuid } from '../vuid/vuid'; import { Maybe } from '../utils/type'; -import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from '../error_messages'; +import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from 'error_message'; import { OptimizelyError } from '../error/optimizly_error'; export interface OdpManager extends Service { diff --git a/lib/odp/segment_manager/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts index 71c300030..d243f2a14 100644 --- a/lib/odp/segment_manager/odp_segment_manager.ts +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -20,7 +20,7 @@ import { OdpIntegrationConfig } from '../odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { ODP_USER_KEY } from '../constant'; import { LoggerFacade } from '../../logging/logger'; -import { ODP_CONFIG_NOT_AVAILABLE, ODP_NOT_INTEGRATED } from '../../error_messages'; +import { ODP_CONFIG_NOT_AVAILABLE, ODP_NOT_INTEGRATED } from 'error_message'; export interface OdpSegmentManager { fetchQualifiedSegments( diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 30d67cd72..1d542beb2 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -47,7 +47,7 @@ import { USER_RECEIVED_DEFAULT_VARIABLE_VALUE, VALID_USER_PROFILE_SERVICE, VARIATION_REMOVED_FOR_USER, -} from '../log_messages'; +} from 'log_message'; import { EXPERIMENT_KEY_NOT_IN_DATAFILE, INVALID_ATTRIBUTES, @@ -59,7 +59,7 @@ import { USER_NOT_IN_FORCED_VARIATION, INSTANCE_CLOSED, ONREADY_TIMEOUT_EXPIRED, -} from '../error_messages'; +} from 'error_message'; import { AUDIENCE_EVALUATION_RESULT_COMBINED, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 87da57af7..b0e3a2f87 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -79,7 +79,7 @@ import { VARIABLE_REQUESTED_WITH_WRONG_TYPE, ONREADY_TIMEOUT, INSTANCE_CLOSED -} from '../error_messages'; +} from 'error_message'; import { FEATURE_ENABLED_FOR_USER, @@ -96,7 +96,7 @@ import { USER_RECEIVED_VARIABLE_VALUE, VALID_USER_PROFILE_SERVICE, VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, -} from '../log_messages'; +} from 'log_message'; import { ErrorNotifier } from '../error/error_notifier'; import { ErrorReporter } from '../error/error_reporter'; diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 92985fa5a..f30a21984 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -33,7 +33,7 @@ import { USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, -} from '../log_messages'; +} from 'log_message'; const getMockEventDispatcher = () => { const dispatcher = { diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index 1f7c62473..354823c58 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -28,13 +28,13 @@ import { DATAFILE_FETCH_REQUEST_FAILED, ERROR_FETCHING_DATAFILE, FAILED_TO_FETCH_DATAFILE, -} from '../error_messages'; +} from 'error_message'; import { ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN, MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS, RESPONSE_STATUS_CODE, SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, -} from '../log_messages'; +} from 'log_message'; import { OptimizelyError } from '../error/optimizly_error'; export class PollingDatafileManager extends BaseService implements DatafileManager { diff --git a/lib/project_config/project_config.tests.js b/lib/project_config/project_config.tests.js index ff8e18624..6e93327cc 100644 --- a/lib/project_config/project_config.tests.js +++ b/lib/project_config/project_config.tests.js @@ -30,7 +30,7 @@ import { VARIABLE_KEY_NOT_IN_DATAFILE, FEATURE_NOT_IN_DATAFILE, UNABLE_TO_CAST_VALUE -} from '../error_messages'; +} from 'error_message'; var createLogger = () => ({ debug: () => {}, diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 1a7ad4313..38a4b42d6 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -48,8 +48,8 @@ import { UNRECOGNIZED_ATTRIBUTE, VARIABLE_KEY_NOT_IN_DATAFILE, VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, -} from '../error_messages'; -import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from '../log_messages'; +} from 'error_message'; +import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from 'log_message'; import { OptimizelyError } from '../error/optimizly_error'; interface TryCreatingProjectConfigConfig { diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index 985a0524e..b9dbf279e 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -22,7 +22,7 @@ import { scheduleMicrotask } from '../utils/microtask'; import { Service, ServiceState, BaseService } from '../service'; import { Consumer, Fn, Transformer } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; -import { DATAFILE_MANAGER_STOPPED, NO_SDKKEY_OR_DATAFILE, DATAFILE_MANAGER_FAILED_TO_START } from '../error_messages'; +import { DATAFILE_MANAGER_STOPPED, NO_SDKKEY_OR_DATAFILE, DATAFILE_MANAGER_FAILED_TO_START } from 'error_message'; import { OptimizelyError } from '../error/optimizly_error'; interface ProjectConfigManagerConfig { diff --git a/lib/utils/attributes_validator/index.tests.js b/lib/utils/attributes_validator/index.tests.js index 17daf68e8..ecfc4bccb 100644 --- a/lib/utils/attributes_validator/index.tests.js +++ b/lib/utils/attributes_validator/index.tests.js @@ -17,7 +17,7 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import * as attributesValidator from './'; -import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from '../../error_messages'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from 'error_message'; describe('lib/utils/attributes_validator', function() { describe('APIs', function() { diff --git a/lib/utils/attributes_validator/index.ts b/lib/utils/attributes_validator/index.ts index adbe70bdb..08b50eb43 100644 --- a/lib/utils/attributes_validator/index.ts +++ b/lib/utils/attributes_validator/index.ts @@ -16,7 +16,7 @@ import { ObjectWithUnknownProperties } from '../../shared_types'; import fns from '../../utils/fns'; -import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from '../../error_messages'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; /** diff --git a/lib/utils/config_validator/index.tests.js b/lib/utils/config_validator/index.tests.js index 8ff6e7581..4df36a83e 100644 --- a/lib/utils/config_validator/index.tests.js +++ b/lib/utils/config_validator/index.tests.js @@ -25,7 +25,7 @@ import { INVALID_EVENT_DISPATCHER, INVALID_LOGGER, NO_DATAFILE_SPECIFIED, -} from '../../error_messages'; +} from 'error_message'; describe('lib/utils/config_validator', function() { describe('APIs', function() { diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index a61d4f1cf..636791613 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -26,7 +26,7 @@ import { INVALID_EVENT_DISPATCHER, INVALID_LOGGER, NO_DATAFILE_SPECIFIED, -} from '../../error_messages'; +} from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE_VERSIONS.V4]; diff --git a/lib/utils/event_tag_utils/index.tests.js b/lib/utils/event_tag_utils/index.tests.js index f1e6e8834..f1f81a1fb 100644 --- a/lib/utils/event_tag_utils/index.tests.js +++ b/lib/utils/event_tag_utils/index.tests.js @@ -18,7 +18,7 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import * as eventTagUtils from './'; -import { FAILED_TO_PARSE_REVENUE, PARSED_REVENUE_VALUE, PARSED_NUMERIC_VALUE, FAILED_TO_PARSE_VALUE } from '../../log_messages'; +import { FAILED_TO_PARSE_REVENUE, PARSED_REVENUE_VALUE, PARSED_NUMERIC_VALUE, FAILED_TO_PARSE_VALUE } from 'log_message'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index 8819086a9..7c4377d76 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -18,7 +18,7 @@ import { FAILED_TO_PARSE_VALUE, PARSED_NUMERIC_VALUE, PARSED_REVENUE_VALUE, -} from '../../log_messages'; +} from 'log_message'; import { EventTags } from '../../event_processor/event_builder/user_event'; import { LoggerFacade } from '../../logging/logger'; diff --git a/lib/utils/event_tags_validator/index.tests.js b/lib/utils/event_tags_validator/index.tests.js index a7ea58956..c18585d50 100644 --- a/lib/utils/event_tags_validator/index.tests.js +++ b/lib/utils/event_tags_validator/index.tests.js @@ -14,10 +14,9 @@ * limitations under the License. */ import { assert } from 'chai'; -import { sprintf } from '../../utils/fns'; import { validate } from './'; -import { INVALID_EVENT_TAGS } from '../../error_messages'; +import { INVALID_EVENT_TAGS } from 'error_message'; describe('lib/utils/event_tags_validator', function() { describe('APIs', function() { diff --git a/lib/utils/event_tags_validator/index.ts b/lib/utils/event_tags_validator/index.ts index 6dde8a045..421321f69 100644 --- a/lib/utils/event_tags_validator/index.ts +++ b/lib/utils/event_tags_validator/index.ts @@ -18,7 +18,7 @@ * Provides utility method for validating that event tags user has provided are valid */ import { OptimizelyError } from '../../error/optimizly_error'; -import { INVALID_EVENT_TAGS } from '../../error_messages'; +import { INVALID_EVENT_TAGS } from 'error_message'; /** * Validates user's provided event tags diff --git a/lib/utils/executor/backoff_retry_runner.ts b/lib/utils/executor/backoff_retry_runner.ts index 93b3ef748..f0b185a99 100644 --- a/lib/utils/executor/backoff_retry_runner.ts +++ b/lib/utils/executor/backoff_retry_runner.ts @@ -1,5 +1,5 @@ import { OptimizelyError } from "../../error/optimizly_error"; -import { RETRY_CANCELLED } from "../../error_messages"; +import { RETRY_CANCELLED } from "error_message"; import { resolvablePromise, ResolvablePromise } from "../promise/resolvablePromise"; import { BackoffController } from "../repeater/repeater"; import { AsyncProducer, Fn } from "../type"; diff --git a/lib/utils/http_request_handler/request_handler.browser.ts b/lib/utils/http_request_handler/request_handler.browser.ts index 5ab8ce1cb..a85137dad 100644 --- a/lib/utils/http_request_handler/request_handler.browser.ts +++ b/lib/utils/http_request_handler/request_handler.browser.ts @@ -17,8 +17,8 @@ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import { LoggerFacade, LogLevel } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; -import { REQUEST_ERROR, REQUEST_TIMEOUT } from '../../error_messages'; -import { UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from '../../log_messages'; +import { REQUEST_ERROR, REQUEST_TIMEOUT } from 'error_message'; +import { UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from 'log_message'; import { OptimizelyError } from '../../error/optimizly_error'; /** diff --git a/lib/utils/http_request_handler/request_handler.node.ts b/lib/utils/http_request_handler/request_handler.node.ts index cf0a620db..7e64a7383 100644 --- a/lib/utils/http_request_handler/request_handler.node.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -20,7 +20,7 @@ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import decompressResponse from 'decompress-response'; import { LoggerFacade } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; -import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from '../../error_messages'; +import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; /** diff --git a/lib/utils/json_schema_validator/index.tests.js b/lib/utils/json_schema_validator/index.tests.js index aaee8dc27..cace62047 100644 --- a/lib/utils/json_schema_validator/index.tests.js +++ b/lib/utils/json_schema_validator/index.tests.js @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../fns'; import { assert } from 'chai'; import { validate } from './'; import testData from '../../tests/test_data'; -import { NO_JSON_PROVIDED } from '../../error_messages'; +import { NO_JSON_PROVIDED } from 'error_message'; describe('lib/utils/json_schema_validator', function() { diff --git a/lib/utils/json_schema_validator/index.ts b/lib/utils/json_schema_validator/index.ts index f5824931c..42fe19f11 100644 --- a/lib/utils/json_schema_validator/index.ts +++ b/lib/utils/json_schema_validator/index.ts @@ -16,7 +16,7 @@ import { JSONSchema4, validate as jsonSchemaValidator } from 'json-schema'; import schema from '../../project_config/project_config_schema'; -import { INVALID_DATAFILE, INVALID_JSON, NO_JSON_PROVIDED } from '../../error_messages'; +import { INVALID_DATAFILE, INVALID_JSON, NO_JSON_PROVIDED } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; /** diff --git a/lib/utils/semantic_version/index.ts b/lib/utils/semantic_version/index.ts index 2e5e02e47..56fad06a5 100644 --- a/lib/utils/semantic_version/index.ts +++ b/lib/utils/semantic_version/index.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; +import { UNKNOWN_MATCH_TYPE } from 'error_message'; import { LoggerFacade } from '../../logging/logger'; import { VERSION_TYPE } from '../enums'; diff --git a/lib/utils/user_profile_service_validator/index.tests.js b/lib/utils/user_profile_service_validator/index.tests.js index 8f2e50b28..d5ad136e8 100644 --- a/lib/utils/user_profile_service_validator/index.tests.js +++ b/lib/utils/user_profile_service_validator/index.tests.js @@ -14,10 +14,9 @@ * limitations under the License. * ***************************************************************************/ import { assert } from 'chai'; -import { sprintf } from '../../utils/fns'; import { validate } from './'; -import { INVALID_USER_PROFILE_SERVICE } from '../../error_messages'; +import { INVALID_USER_PROFILE_SERVICE } from 'error_message'; describe('lib/utils/user_profile_service_validator', function() { describe('APIs', function() { diff --git a/lib/utils/user_profile_service_validator/index.ts b/lib/utils/user_profile_service_validator/index.ts index cb7529dcb..95e8cf61a 100644 --- a/lib/utils/user_profile_service_validator/index.ts +++ b/lib/utils/user_profile_service_validator/index.ts @@ -19,7 +19,7 @@ */ import { ObjectWithUnknownProperties } from '../../shared_types'; -import { INVALID_USER_PROFILE_SERVICE } from '../../error_messages'; +import { INVALID_USER_PROFILE_SERVICE } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; diff --git a/package-lock.json b/package-lock.json index 07043aa32..4cfbad348 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "ts-loader": "^9.3.1", "ts-mockito": "^2.6.1", "ts-node": "^8.10.2", + "tsconfig-paths": "^4.2.0", "tslib": "^2.4.0", "typescript": "^4.7.4", "vitest": "^2.0.5", @@ -15800,6 +15801,29 @@ "source-map": "^0.6.0" } }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", diff --git a/package.json b/package.json index 367d40125..2d97998df 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "clean:win": "(if exist dist rd /s/q dist)", "lint": "tsc --noEmit && eslint 'lib/**/*.js' 'lib/**/*.ts'", "test-vitest": "tsc --noEmit --p tsconfig.spec.json && vitest run", - "test-mocha": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js'", + "test-mocha": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r tsconfig-paths/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js'", "test": "npm run test-mocha && npm run test-vitest", "posttest": "npm run lint", "test-ci": "npm run test-xbrowser && npm run test-umdbrowser", @@ -82,14 +82,14 @@ "test-umdbrowser": "npm run build-browser-umd && karma start karma.umd.conf.js --single-run", "test-karma-local": "karma start karma.local_chrome.bs.conf.js && npm run build-browser-umd && karma start karma.local_chrome.umd.conf.js", "prebuild": "npm run clean", - "build": "rollup -c && cp dist/index.lite.d.ts dist/optimizely.lite.es.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.es.min.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.min.d.ts", - "build:win": "rollup -c && type nul > dist/optimizely.lite.es.d.ts && type nul > dist/optimizely.lite.es.min.d.ts && type nul > dist/optimizely.lite.min.d.ts", + "build": "npm run genmsg && rollup -c && cp dist/index.lite.d.ts dist/optimizely.lite.es.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.es.min.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.min.d.ts", + "build:win": "npm run genmsg && rollup -c && type nul > dist/optimizely.lite.es.d.ts && type nul > dist/optimizely.lite.es.min.d.ts && type nul > dist/optimizely.lite.min.d.ts", "build-browser-umd": "rollup -c --config-umd", "coveralls": "nyc --reporter=lcov npm test", "prepare": "npm run build", "prepublishOnly": "npm test && npm run test-ci", "postbuild:win": "@powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.min.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.min.d.ts\"", - "genmsg": "jiti message_generator ./lib/error_messages.ts ./lib/log_messages.ts ./lib/exception_messages.ts" + "genmsg": "jiti message_generator ./lib/message/error_message.ts ./lib/message/log_message.ts" }, "repository": { "type": "git", @@ -157,6 +157,7 @@ "ts-loader": "^9.3.1", "ts-mockito": "^2.6.1", "ts-node": "^8.10.2", + "tsconfig-paths": "^4.2.0", "tslib": "^2.4.0", "typescript": "^4.7.4", "vitest": "^2.0.5", diff --git a/rollup.config.js b/rollup.config.js index 8a7887714..68d495c9c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -31,6 +31,21 @@ const typescriptPluginOptions = { 'node_modules', ], include: ['./lib/**/*.ts', './lib/**/*.js'], + tsconfigOverride: { + compilerOptions: { + paths: { + "*": [ + "./typings/*" + ], + "error_message": [ + "./lib/message/error_message.gen" + ], + "log_message": [ + "./lib/message/log_message.gen" + ], + } + } + } }; const cjsBundleFor = platform => ({ diff --git a/tsconfig.json b/tsconfig.json index ef8012773..c69f440b6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,12 @@ "*": [ "./typings/*" ], + "error_message": [ + "./lib/message/error_message" + ], + "log_message": [ + "./lib/message/log_message" + ], }, "resolveJsonModule": true, "allowJs": true, diff --git a/vitest.config.mts b/vitest.config.mts index 673f7d1c6..05669feb1 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -13,10 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import path from 'path'; import { defineConfig } from 'vitest/config' export default defineConfig({ + resolve: { + alias: { + 'error_message': path.resolve(__dirname, './lib/message/error_message'), + 'log_message': path.resolve(__dirname, './lib/message/log_message'), + }, + }, test: { onConsoleLog: () => true, environment: 'happy-dom', From ba28ba65bc1089fc8c3e11330b4778a3cc4032b8 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 24 Jan 2025 21:17:02 +0600 Subject: [PATCH 115/200] [FSSDK-11090] remove unused log messages (#988) --- lib/core/decision_service/index.ts | 12 +++-- lib/message/error_message.ts | 53 +++---------------- lib/message/log_message.ts | 30 ++--------- lib/odp/event_manager/odp_event_manager.ts | 4 +- lib/optimizely/index.tests.js | 6 +-- lib/optimizely/index.ts | 4 +- lib/optimizely_user_context/index.tests.js | 2 +- .../polling_datafile_manager.ts | 1 - lib/project_config/project_config.ts | 4 +- .../request_handler.browser.ts | 3 +- 10 files changed, 31 insertions(+), 88 deletions(-) diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index beb6b24da..21a63b763 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -61,10 +61,6 @@ import { import { SAVED_USER_VARIATION, SAVED_VARIATION_NOT_FOUND, - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, USER_HAS_NO_FORCED_VARIATION, USER_MAPPED_TO_FORCED_VARIATION, USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, @@ -98,6 +94,14 @@ export const IMPROPERLY_FORMATTED_EXPERIMENT = 'Experiment key %s is improperly export const USER_HAS_FORCED_VARIATION = 'Variation %s is mapped to experiment %s and user %s in the forced variation map.'; export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = 'User %s meets conditions for targeting rule %s.'; +export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = + 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = + 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = + 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = + 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; export interface DecisionObj { experiment: Experiment | null; diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index 97844f639..0dcb28567 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,9 @@ * limitations under the License. */ export const NOTIFICATION_LISTENER_EXCEPTION = 'Notification listener for (%s) threw exception: %s'; -export const BROWSER_ODP_MANAGER_INITIALIZATION_FAILED = '%s: Error initializing Browser ODP Manager.'; export const CONDITION_EVALUATOR_ERROR = 'Error evaluating audience condition of type %s: %s'; -export const DATAFILE_AND_SDK_KEY_MISSING = - '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely'; export const EXPERIMENT_KEY_NOT_IN_DATAFILE = 'Experiment key %s is not in datafile.'; export const FEATURE_NOT_IN_DATAFILE = 'Feature key %s is not in datafile.'; -export const FETCH_SEGMENTS_FAILED_NETWORK_ERROR = '%s: Audience segments fetch failed. (network error)'; -export const FETCH_SEGMENTS_FAILED_DECODE_ERROR = '%s: Audience segments fetch failed. (decode error)'; export const INVALID_ATTRIBUTES = 'Provided attributes are in an invalid format.'; export const INVALID_BUCKETING_ID = 'Unable to generate hash for bucketing ID %s: %s'; export const INVALID_DATAFILE = 'Datafile is invalid - property %s: %s'; @@ -38,45 +33,18 @@ export const INVALID_GROUP_ID = 'Group ID %s is not in datafile.'; export const INVALID_LOGGER = 'Provided "logger" is in an invalid format.'; export const INVALID_USER_ID = 'Provided user ID is in an invalid format.'; export const INVALID_USER_PROFILE_SERVICE = 'Provided user profile service instance is in an invalid format: %s.'; -export const LOCAL_STORAGE_DOES_NOT_EXIST = 'Error accessing window localStorage.'; export const MISSING_INTEGRATION_KEY = 'Integration key missing from datafile. All integrations should include a key.'; export const NO_DATAFILE_SPECIFIED = 'No datafile specified. Cannot start optimizely.'; export const NO_JSON_PROVIDED = 'No JSON object to validate against schema.'; export const NO_EVENT_PROCESSOR = 'No event processor is provided'; export const NO_VARIATION_FOR_EXPERIMENT_KEY = 'No variation key %s defined in datafile for experiment %s.'; -export const ODP_CONFIG_NOT_AVAILABLE = '%s: ODP is not integrated to the project.'; +export const ODP_CONFIG_NOT_AVAILABLE = 'ODP config is not available.'; export const ODP_EVENT_FAILED = 'ODP event send failed.'; export const ODP_EVENT_MANAGER_IS_NOT_RUNNING = 'ODP event manager is not running.'; export const ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE = 'ODP events should have at least one key-value pair in identifiers.'; -export const ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING = - '%s: ODP unable to fetch qualified segments (Segments Manager not initialized).'; -export const ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING = - '%s: ODP identify event %s is not dispatched (Event Manager not instantiated).'; -export const ODP_INITIALIZATION_FAILED = '%s: ODP failed to initialize.'; -export const ODP_INVALID_DATA = '%s: ODP data is not valid'; -export const ODP_EVENT_FAILED_ODP_MANAGER_MISSING = '%s: ODP Event failed to send. (ODP Manager not initialized).'; -export const ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING = - '%s: ODP failed to Fetch Qualified Segments. (ODP Manager not initialized).'; -export const ODP_IDENTIFY_USER_FAILED_ODP_MANAGER_MISSING = - '%s: ODP failed to Identify User. (ODP Manager not initialized).'; -export const ODP_IDENTIFY_USER_FAILED_USER_CONTEXT_INITIALIZATION = - '%s: ODP failed to Identify User. (Failed during User Context Initialization).'; -export const ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING = - '%s: ODP Manager failed to update OdpConfig settings for internal event manager. (Event Manager not initialized).'; -export const ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING = - '%s: ODP Manager failed to update OdpConfig settings for internal segments manager. (Segments Manager not initialized).'; -export const ODP_NOT_ENABLED = 'ODP is not enabled'; -export const ODP_NOT_INTEGRATED = '%s: ODP is not integrated'; -export const ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING = - '%s: ODP send event %s was not dispatched (Event Manager not instantiated).'; -export const ODP_SEND_EVENT_FAILED_UID_MISSING = - '%s: ODP send event %s was not dispatched (No valid user identifier provided).'; -export const ODP_SEND_EVENT_FAILED_VUID_MISSING = '%s: ODP send event %s was not dispatched (Unable to fetch VUID).'; -export const ODP_VUID_INITIALIZATION_FAILED = '%s: ODP VUID initialization failed.'; -export const ODP_VUID_REGISTRATION_FAILED = '%s: ODP VUID failed to be registered.'; -export const ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING = - '%s: ODP register vuid failed. (Event Manager not instantiated).'; +export const ODP_EVENT_FAILED_ODP_MANAGER_MISSING = 'ODP Event failed to send. (ODP Manager not available).'; +export const ODP_NOT_INTEGRATED = 'ODP is not integrated'; export const UNDEFINED_ATTRIBUTE = 'Provided attribute: %s has an undefined value.'; export const UNRECOGNIZED_ATTRIBUTE = 'Unrecognized attribute %s provided. Pruning before sending event to Optimizely.'; @@ -86,17 +54,15 @@ export const USER_NOT_IN_FORCED_VARIATION = export const USER_PROFILE_LOOKUP_ERROR = 'Error while looking up user profile for user ID "%s": %s.'; export const USER_PROFILE_SAVE_ERROR = 'Error while saving user profile for user ID "%s": %s.'; export const VARIABLE_KEY_NOT_IN_DATAFILE = - '%s: Variable with key "%s" associated with feature with key "%s" is not in datafile.'; -export const VARIATION_ID_NOT_IN_DATAFILE = '%s: No variation ID %s defined in datafile for experiment %s.'; -export const VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT = 'Variation ID %s is not in the datafile.'; + 'Variable with key "%s" associated with feature with key "%s" is not in datafile.'; +export const VARIATION_ID_NOT_IN_DATAFILE = 'Variation ID %s is not in the datafile.'; export const INVALID_INPUT_FORMAT = 'Provided %s is in an invalid format.'; export const INVALID_DATAFILE_VERSION = 'This version of the JavaScript SDK does not support the given datafile version: %s'; export const INVALID_VARIATION_KEY = 'Provided variation key is in an invalid format.'; -export const UNABLE_TO_GET_VUID = 'Unable to get VUID - ODP Manager is not instantiated yet.'; export const ERROR_FETCHING_DATAFILE = 'Error fetching datafile: %s'; export const DATAFILE_FETCH_REQUEST_FAILED = 'Datafile fetch request failed with status: %s'; -export const EVENT_DATA_FOUND_TO_BE_INVALID = 'Event data found to be invalid.'; +export const EVENT_DATA_INVALID = 'Event data invalid.'; export const EVENT_ACTION_INVALID = 'Event action invalid.'; export const FAILED_TO_SEND_ODP_EVENTS = 'failed to send odp events'; export const UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE = 'Unable to get VUID - VuidManager is not available' @@ -135,13 +101,10 @@ export const SEND_BEACON_FAILED = 'sendBeacon failed'; export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' export const FAILED_TO_DISPATCH_EVENTS_WITH_ARG = 'Failed to dispatch events: %s'; export const EVENT_PROCESSOR_STOPPED = 'Event processor stopped before it could be started'; -export const CANNOT_START_WITHOUT_ODP_CONFIG = 'cannot start without ODP config'; -export const START_CALLED_WHEN_ODP_IS_NOT_INTEGRATED = 'start() called when ODP is not integrated'; -export const ODP_ACTION_IS_NOT_VALID = 'ODP action is not valid (cannot be empty).'; export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before running'; export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; -export const ONREADY_TIMEOUT_EXPIRED = 'onReady timeout expired after %s ms'; export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to page unload event: "%s"'; +export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; export const messages: string[] = []; diff --git a/lib/message/log_message.ts b/lib/message/log_message.ts index bbd1d110e..b4dc35650 100644 --- a/lib/message/log_message.ts +++ b/lib/message/log_message.ts @@ -14,47 +14,24 @@ * limitations under the License. */ -export const ACTIVATE_USER = '%s: Activating user %s in experiment %s.'; -export const DISPATCH_CONVERSION_EVENT = '%s: Dispatching conversion event to URL %s with params %s.'; -export const DISPATCH_IMPRESSION_EVENT = '%s: Dispatching impression event to URL %s with params %s.'; -export const DEPRECATED_EVENT_VALUE = '%s: Event value is deprecated in %s call.'; export const FEATURE_ENABLED_FOR_USER = 'Feature %s is enabled for user %s.'; export const FEATURE_NOT_ENABLED_FOR_USER = 'Feature %s is not enabled for user %s.'; -export const FAILED_TO_PARSE_VALUE = '%s: Failed to parse event value "%s" from event tags.'; +export const FAILED_TO_PARSE_VALUE = 'Failed to parse event value "%s" from event tags.'; export const FAILED_TO_PARSE_REVENUE = 'Failed to parse revenue value "%s" from event tags.'; export const INVALID_CLIENT_ENGINE = 'Invalid client engine passed: %s. Defaulting to node-sdk.'; -export const INVALID_DEFAULT_DECIDE_OPTIONS = '%s: Provided default decide options is not an array.'; +export const INVALID_DEFAULT_DECIDE_OPTIONS = 'Provided default decide options is not an array.'; export const INVALID_DECIDE_OPTIONS = 'Provided decide options is not an array. Using default decide options.'; export const NOT_ACTIVATING_USER = 'Not activating user %s for experiment %s.'; -export const ODP_DISABLED = 'ODP Disabled.'; -export const ODP_IDENTIFY_FAILED_ODP_DISABLED = '%s: ODP identify event for user %s is not dispatched (ODP disabled).'; -export const ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED = - '%s: ODP identify event %s is not dispatched (ODP not integrated).'; -export const ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED = - '%s: sendOdpEvent failed to parse through and convert fs_user_id aliases'; export const PARSED_REVENUE_VALUE = 'Parsed revenue value "%s" from event tags.'; export const PARSED_NUMERIC_VALUE = 'Parsed event value "%s" from event tags.'; export const SAVED_USER_VARIATION = 'Saved user profile for user "%s".'; -export const UPDATED_USER_VARIATION = '%s: Updated variation "%s" of experiment "%s" for user "%s".'; export const SAVED_VARIATION_NOT_FOUND = 'User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.'; export const SHOULD_NOT_DISPATCH_ACTIVATE = 'Experiment %s is not in "Running" state. Not activating user.'; export const SKIPPING_JSON_VALIDATION = 'Skipping JSON schema validation.'; export const TRACK_EVENT = 'Tracking event %s for user %s.'; -export const USER_IN_FEATURE_EXPERIMENT = '%s: User %s is in variation %s of experiment %s on the feature %s.'; -export const USER_NOT_BUCKETED_INTO_EVERYONE_TARGETING_RULE = - '%s: User %s not bucketed into everyone targeting rule due to traffic allocation.'; -export const USER_NOT_BUCKETED_INTO_ANY_EXPERIMENT_IN_GROUP = '%s: User %s is not in any experiment of group %s.'; export const USER_MAPPED_TO_FORCED_VARIATION = 'Set variation %s for experiment %s and user %s in the forced variation map.'; -export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = - 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; -export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = - 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.'; -export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = - 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; -export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = - 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; export const USER_HAS_NO_FORCED_VARIATION = 'User %s is not in the forced variation map.'; export const USER_RECEIVED_DEFAULT_VARIABLE_VALUE = 'User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".'; @@ -75,12 +52,13 @@ export const MISSING_ATTRIBUTE_VALUE = export const UNEXPECTED_TYPE_NULL = 'Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".'; export const UPDATED_OPTIMIZELY_CONFIG = 'Updated Optimizely config to revision %s (project id %s)'; -export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; export const ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN = 'Adding Authorization header with Bearer Token'; export const MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS = 'Making datafile request to url %s with headers: %s'; export const RESPONSE_STATUS_CODE = 'Response status code: %s'; export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = 'No experiment %s mapped to user %s in the forced variation map.'; +export const INVALID_EXPERIMENT_KEY_INFO = + 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; export const messages: string[] = []; diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 11d4b37f1..75c1a632c 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -25,7 +25,7 @@ import { isSuccessStatusCode } from '../../utils/http_request_handler/http_util' import { ODP_DEFAULT_EVENT_TYPE, ODP_USER_KEY } from '../constant'; import { EVENT_ACTION_INVALID, - EVENT_DATA_FOUND_TO_BE_INVALID, + EVENT_DATA_INVALID, FAILED_TO_SEND_ODP_EVENTS, ODP_EVENT_MANAGER_IS_NOT_RUNNING, ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE, @@ -179,7 +179,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } if (!this.isDataValid(event.data)) { - this.logger?.error(EVENT_DATA_FOUND_TO_BE_INVALID); + this.logger?.error(EVENT_DATA_INVALID); return; } diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 1d542beb2..3f5e536ba 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -58,7 +58,7 @@ import { NO_VARIATION_FOR_EXPERIMENT_KEY, USER_NOT_IN_FORCED_VARIATION, INSTANCE_CLOSED, - ONREADY_TIMEOUT_EXPIRED, + ONREADY_TIMEOUT, } from 'error_message'; import { @@ -9466,7 +9466,7 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); }, (err) => { - assert.equal(err.baseMessage, ONREADY_TIMEOUT_EXPIRED); + assert.equal(err.baseMessage, ONREADY_TIMEOUT); assert.deepEqual(err.params, [ 500 ]); }); }); @@ -9491,7 +9491,7 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { - assert.equal(err.baseMessage, ONREADY_TIMEOUT_EXPIRED); + assert.equal(err.baseMessage, ONREADY_TIMEOUT); assert.deepEqual(err.params, [ 30000 ]); }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index b0e3a2f87..351b538d7 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -66,7 +66,6 @@ import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; import { FEATURE_NOT_IN_DATAFILE, - INVALID_EXPERIMENT_KEY, INVALID_INPUT_FORMAT, NO_EVENT_PROCESSOR, ODP_EVENT_FAILED, @@ -88,6 +87,7 @@ import { INVALID_CLIENT_ENGINE, INVALID_DECIDE_OPTIONS, INVALID_DEFAULT_DECIDE_OPTIONS, + INVALID_EXPERIMENT_KEY_INFO, NOT_ACTIVATING_USER, SHOULD_NOT_DISPATCH_ACTIVATE, TRACK_EVENT, @@ -452,7 +452,7 @@ export default class Optimizely implements Client { const experiment = configObj.experimentKeyMap[experimentKey]; if (!experiment || experiment.isRollout) { - this.logger?.debug(INVALID_EXPERIMENT_KEY, experimentKey); + this.logger?.debug(INVALID_EXPERIMENT_KEY_INFO, experimentKey); return null; } diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index f30a21984..a71be212e 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -33,7 +33,7 @@ import { USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, -} from 'log_message'; +} from '../core/decision_service'; const getMockEventDispatcher = () => { const dispatcher = { diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index 354823c58..a8fb9128b 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -115,7 +115,6 @@ export class PollingDatafileManager extends BaseService implements DatafileManag this.startPromise.reject(new OptimizelyError(DATAFILE_MANAGER_STOPPED)); } - this.logger?.debug(DATAFILE_MANAGER_STOPPED); this.state = ServiceState.Terminated; this.repeater.stop(); this.currentRequest?.abort(); diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 38a4b42d6..a41347916 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -47,7 +47,7 @@ import { UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, UNRECOGNIZED_ATTRIBUTE, VARIABLE_KEY_NOT_IN_DATAFILE, - VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, + VARIATION_ID_NOT_IN_DATAFILE, } from 'error_message'; import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from 'log_message'; import { OptimizelyError } from '../error/optimizly_error'; @@ -693,7 +693,7 @@ export const getVariableValueForVariation = function( } if (!projectConfig.variationVariableUsageMap.hasOwnProperty(variation.id)) { - logger?.error(VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, variation.id); + logger?.error(VARIATION_ID_NOT_IN_DATAFILE, variation.id); return null; } diff --git a/lib/utils/http_request_handler/request_handler.browser.ts b/lib/utils/http_request_handler/request_handler.browser.ts index a85137dad..340dcca33 100644 --- a/lib/utils/http_request_handler/request_handler.browser.ts +++ b/lib/utils/http_request_handler/request_handler.browser.ts @@ -17,8 +17,7 @@ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import { LoggerFacade, LogLevel } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; -import { REQUEST_ERROR, REQUEST_TIMEOUT } from 'error_message'; -import { UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from 'log_message'; +import { REQUEST_ERROR, REQUEST_TIMEOUT, UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; /** From 281930a8a122c3d8ab6285f8c4c4cacffb5142ee Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 27 Jan 2025 19:39:26 +0600 Subject: [PATCH 116/200] [FSSDK-11090] remove unused plugins directory (#989) --- lib/core/decision_service/index.tests.js | 6 +- lib/index.browser.ts | 3 - lib/index.node.ts | 3 - lib/index.react_native.spec.ts | 1 - lib/index.react_native.ts | 3 - lib/notification_center/index.tests.js | 7 +- lib/optimizely/index.tests.js | 122 +++++++----------- lib/optimizely_user_context/index.tests.js | 14 +- lib/plugins/error_handler/index.tests.js | 28 ---- lib/plugins/error_handler/index.ts | 26 ---- .../logger/index.react_native.tests.js | 82 ------------ lib/plugins/logger/index.react_native.ts | 60 --------- lib/plugins/logger/index.tests.js | 112 ---------------- 13 files changed, 52 insertions(+), 415 deletions(-) delete mode 100644 lib/plugins/error_handler/index.tests.js delete mode 100644 lib/plugins/error_handler/index.ts delete mode 100644 lib/plugins/logger/index.react_native.tests.js delete mode 100644 lib/plugins/logger/index.react_native.ts delete mode 100644 lib/plugins/logger/index.tests.js diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 89b7113eb..431b95efa 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -30,7 +30,6 @@ import Optimizely from '../../optimizely'; import OptimizelyUserContext from '../../optimizely_user_context'; import projectConfig, { createProjectConfig } from '../../project_config/project_config'; import AudienceEvaluator from '../audience_evaluator'; -import errorHandler from '../../plugins/error_handler'; import eventDispatcher from '../../event_processor/event_dispatcher/default_dispatcher.browser'; import * as jsonSchemaValidator from '../../utils/json_schema_validator'; import { getMockProjectConfigManager } from '../../tests/mock/mock_project_config_manager'; @@ -1053,17 +1052,14 @@ describe('lib/core/decision_service', function() { isValidInstance: true, logger: createdLogger, eventProcessor: getForwardingEventProcessor(eventDispatcher), - notificationCenter: createNotificationCenter(createdLogger, errorHandler), - errorHandler: errorHandler, + notificationCenter: createNotificationCenter(createdLogger), }); sinon.stub(eventDispatcher, 'dispatchEvent'); - sinon.stub(errorHandler, 'handleError'); }); afterEach(function() { eventDispatcher.dispatchEvent.restore(); - errorHandler.handleError.restore(); }); var testUserAttributes = { diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 48c996cbd..848524f48 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -15,7 +15,6 @@ */ import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; @@ -97,7 +96,6 @@ const __internalResetRetryState = function(): void { }; export { - defaultErrorHandler as errorHandler, defaultEventDispatcher as eventDispatcher, sendBeaconEventDispatcher, enums, @@ -119,7 +117,6 @@ export * from './common_exports'; export default { ...commonExports, - errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, sendBeaconEventDispatcher, enums, diff --git a/lib/index.node.ts b/lib/index.node.ts index f66abcf28..c0d7b41db 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -18,7 +18,6 @@ import Optimizely from './optimizely'; import * as enums from './utils/enums'; import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; @@ -73,7 +72,6 @@ const createInstance = function(config: Config): Client | null { * Entry point into the Optimizely Node testing SDK */ export { - defaultErrorHandler as errorHandler, defaultEventDispatcher as eventDispatcher, enums, createInstance, @@ -91,7 +89,6 @@ export * from './common_exports'; export default { ...commonExports, - errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, enums, createInstance, diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index 8132b9e76..42ba24821 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -40,7 +40,6 @@ describe('javascript-sdk/react-native', () => { describe('APIs', () => { it('should expose logger, errorHandler, eventDispatcher and enums', () => { - expect(optimizelyFactory.errorHandler).toBeDefined(); expect(optimizelyFactory.eventDispatcher).toBeDefined(); expect(optimizelyFactory.enums).toBeDefined(); }); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index bfbea0aca..243d1fea3 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -17,7 +17,6 @@ import * as enums from './utils/enums'; import Optimizely from './optimizely'; import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; @@ -80,7 +79,6 @@ const createInstance = function(config: Config): Client | null { * Entry point into the Optimizely Javascript SDK for React Native */ export { - defaultErrorHandler as errorHandler, defaultEventDispatcher as eventDispatcher, enums, createInstance, @@ -98,7 +96,6 @@ export * from './common_exports'; export default { ...commonExports, - errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, enums, createInstance, diff --git a/lib/notification_center/index.tests.js b/lib/notification_center/index.tests.js index a7bf83cee..11e6da2bb 100644 --- a/lib/notification_center/index.tests.js +++ b/lib/notification_center/index.tests.js @@ -18,9 +18,7 @@ import { assert } from 'chai'; import { createNotificationCenter } from './'; import * as enums from '../utils/enums'; -import errorHandler from '../plugins/error_handler'; import { NOTIFICATION_TYPES } from './type'; -import { create } from 'lodash'; var LOG_LEVEL = enums.LOG_LEVEL; @@ -35,20 +33,17 @@ var createLogger = () => ({ describe('lib/core/notification_center', function() { describe('APIs', function() { var mockLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - var mockErrorHandler = errorHandler.handleError; var mockLoggerStub; - var mockErrorHandlerStub; + var notificationCenterInstance; var sandbox; beforeEach(function() { sandbox = sinon.sandbox.create(); mockLoggerStub = sandbox.stub(mockLogger, 'log'); - mockErrorHandlerStub = sandbox.stub(mockErrorHandler, 'handleError'); notificationCenterInstance = createNotificationCenter({ logger: mockLoggerStub, - errorHandler: mockErrorHandlerStub, }); }); diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 3f5e536ba..f2a739a04 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -22,9 +22,7 @@ import OptimizelyUserContext from '../optimizely_user_context'; import { OptimizelyDecideOption } from '../shared_types'; import AudienceEvaluator from '../core/audience_evaluator'; import * as bucketer from '../core/bucketer'; -import * as projectConfigManager from '../project_config/project_config_manager'; import * as enums from '../utils/enums'; -import errorHandler from '../plugins/error_handler'; import fns from '../utils/fns'; import * as decisionService from '../core/decision_service'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; @@ -155,17 +153,15 @@ describe('lib/optimizely', function() { }); describe('constructor', function() { - var stubErrorHandler = { handleError: function() {} }; var stubEventDispatcher = { dispatchEvent: function() { return Promise.resolve(null); }, }; var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: stubErrorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); var eventProcessor = getForwardingEventProcessor(stubEventDispatcher); beforeEach(function() { - sinon.stub(stubErrorHandler, 'handleError'); sinon.stub(createdLogger, 'debug'); sinon.stub(createdLogger, 'info'); sinon.stub(createdLogger, 'warn'); @@ -173,7 +169,6 @@ describe('lib/optimizely', function() { }); afterEach(function() { - stubErrorHandler.handleError.restore(); createdLogger.debug.restore(); createdLogger.info.restore(); createdLogger.warn.restore(); @@ -184,7 +179,6 @@ describe('lib/optimizely', function() { it('should log if the client engine passed in is invalid', function() { new Optimizely({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: stubErrorHandler, eventDispatcher: stubEventDispatcher, logger: createdLogger, notificationCenter, @@ -200,7 +194,7 @@ describe('lib/optimizely', function() { new Optimizely({ projectConfigManager: getMockProjectConfigManager(), clientEngine: 'node-sdk', - errorHandler: stubErrorHandler, + eventDispatcher: stubEventDispatcher, logger: createdLogger, defaultDecideOptions: 'invalid_options', @@ -216,7 +210,6 @@ describe('lib/optimizely', function() { var instance = new Optimizely({ projectConfigManager: getMockProjectConfigManager(), clientEngine: 'react-sdk', - errorHandler: stubErrorHandler, eventDispatcher: stubEventDispatcher, logger: createdLogger, notificationCenter, @@ -300,7 +293,7 @@ describe('lib/optimizely', function() { var bucketStub; var fakeDecisionResponse; var eventDispatcher = getMockEventDispatcher(); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); var eventProcessor = getForwardingEventProcessor(eventDispatcher, notificationCenter); var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, @@ -314,7 +307,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -324,7 +317,7 @@ describe('lib/optimizely', function() { }); bucketStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(errorHandler, 'handleError'); + sinon.stub(createdLogger, 'debug'); sinon.stub(createdLogger, 'info'); sinon.stub(createdLogger, 'warn'); @@ -335,7 +328,7 @@ describe('lib/optimizely', function() { afterEach(function() { eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); - errorHandler.handleError.restore(); + createdLogger.debug.restore(); createdLogger.info.restore(); createdLogger.warn.restore(); @@ -927,7 +920,6 @@ describe('lib/optimizely', function() { var instance = new Optimizely({ projectConfigManager: mockConfigManager, - errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createLogger({ @@ -1652,7 +1644,7 @@ describe('lib/optimizely', function() { 'testUser' ); - sinon.assert.notCalled(errorHandler.handleError); + // sinon.assert.notCalled(errorHandler.handleError); }); it('should throw an error for invalid attributes', function() { @@ -1671,7 +1663,7 @@ describe('lib/optimizely', function() { it('should not throw an error for an event key without associated experiment IDs', function() { optlyInstance.track('testEventWithoutExperiments', 'testUser'); - sinon.assert.notCalled(errorHandler.handleError); + // sinon.assert.notCalled(errorHandler.handleError); }); it('should track when logger is in DEBUG mode', function() { @@ -1681,7 +1673,6 @@ describe('lib/optimizely', function() { var instance = new Optimizely({ projectConfigManager: mockConfigManager, - errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createLogger({ @@ -2539,7 +2530,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -2602,7 +2593,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, @@ -2659,7 +2650,7 @@ describe('lib/optimizely', function() { var optly = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -2700,7 +2691,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -4308,7 +4299,7 @@ describe('lib/optimizely', function() { logLevel: LOG_LEVEL.INFO, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); var eventDispatcher = getMockEventDispatcher(); var eventProcessor = getForwardingEventProcessor( eventDispatcher, @@ -4323,7 +4314,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -4334,7 +4324,7 @@ describe('lib/optimizely', function() { }); bucketStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(errorHandler, 'handleError'); + sinon.stub(createdLogger, 'debug'); sinon.stub(createdLogger, 'info'); sinon.stub(createdLogger, 'warn'); @@ -4345,7 +4335,7 @@ describe('lib/optimizely', function() { afterEach(function() { eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); - errorHandler.handleError.restore(); + createdLogger.debug.restore(); createdLogger.info.restore(); createdLogger.warn.restore(); @@ -4445,7 +4435,7 @@ describe('lib/optimizely', function() { })); - sinon.stub(errorHandler, 'handleError'); + sinon.stub(createdLogger, 'debug'); sinon.stub(createdLogger, 'info'); sinon.stub(createdLogger, 'warn'); @@ -4455,7 +4445,7 @@ describe('lib/optimizely', function() { afterEach(function() { eventDispatcher.dispatchEvent.reset(); - errorHandler.handleError.restore(); + createdLogger.debug.restore(); createdLogger.info.restore(); createdLogger.warn.restore(); @@ -4972,7 +4962,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -5041,7 +5031,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -5099,7 +5089,7 @@ describe('lib/optimizely', function() { var optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, @@ -5526,7 +5516,7 @@ describe('lib/optimizely', function() { optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, @@ -5573,7 +5563,7 @@ describe('lib/optimizely', function() { optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, @@ -5624,7 +5614,7 @@ describe('lib/optimizely', function() { optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, @@ -5751,7 +5741,7 @@ describe('lib/optimizely', function() { initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), }), userProfileService: userProfileServiceInstance, - errorHandler: errorHandler, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -5993,7 +5983,7 @@ describe('lib/optimizely', function() { initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), }), userProfileService: userProfileServiceInstance, - errorHandler: errorHandler, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -6054,7 +6044,7 @@ describe('lib/optimizely', function() { logLevel: LOG_LEVEL.INFO, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); var eventDispatcher = getMockEventDispatcher(); var eventProcessor = getForwardingEventProcessor( eventDispatcher, @@ -6067,7 +6057,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -6123,7 +6113,7 @@ describe('lib/optimizely', function() { }); var optlyInstance; var fakeDecisionResponse; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); var eventDispatcher = { dispatchEvent: () => Promise.resolve({ statusCode: 200 }), }; @@ -6140,7 +6130,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -6150,7 +6140,6 @@ describe('lib/optimizely', function() { }); sandbox.stub(eventDispatcher, 'dispatchEvent'); - sandbox.stub(errorHandler, 'handleError'); sandbox.stub(createdLogger, 'log'); sandbox.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); sandbox.stub(fns, 'currentTimestamp').returns(1509489766569); @@ -6171,7 +6160,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -6726,7 +6714,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -6770,7 +6757,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -8780,7 +8766,6 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableBoolean when optimizely object is not a valid instance', function() { var instance = new Optimizely({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, @@ -8794,7 +8779,6 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableDouble when optimizely object is not a valid instance', function() { var instance = new Optimizely({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, @@ -8808,7 +8792,6 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableInteger when optimizely object is not a valid instance', function() { var instance = new Optimizely({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, @@ -8822,7 +8805,6 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableString when optimizely object is not a valid instance', function() { var instance = new Optimizely({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, @@ -8836,7 +8818,6 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariableJSON when optimizely object is not a valid instance', function() { var instance = new Optimizely({ projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, @@ -8856,7 +8837,7 @@ describe('lib/optimizely', function() { logToConsole: false, }); var optlyInstance; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); var eventDispatcher = { dispatchEvent: () => Promise.resolve({ statusCode: 200 }), }; @@ -8871,7 +8852,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -8882,7 +8863,6 @@ describe('lib/optimizely', function() { }); sandbox.stub(eventDispatcher, 'dispatchEvent'); - sandbox.stub(errorHandler, 'handleError'); sandbox.stub(createdLogger, 'log'); }); @@ -9002,7 +8982,7 @@ describe('lib/optimizely', function() { }); var optlyInstance; var audienceEvaluator; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); var eventDispatcher = { dispatchEvent: () => Promise.resolve({ statusCode: 200 }), }; @@ -9017,7 +8997,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9029,7 +9009,7 @@ describe('lib/optimizely', function() { audienceEvaluator = AudienceEvaluator.prototype; sandbox.stub(eventDispatcher, 'dispatchEvent'); - sandbox.stub(errorHandler, 'handleError'); + sandbox.stub(createdLogger, 'log'); evalSpy = sandbox.spy(audienceEvaluator, 'evaluate'); }); @@ -9205,13 +9185,13 @@ describe('lib/optimizely', function() { beforeEach(function() { bucketStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(errorHandler, 'handleError'); + sinon.stub(createdLogger, 'debug'); sinon.stub(createdLogger, 'info'); sinon.stub(createdLogger, 'warn'); sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + notificationCenter = createNotificationCenter({ logger: createdLogger, }); eventDispatcher = getMockEventDispatcher(); eventProcessor = getForwardingEventProcessor( eventDispatcher, @@ -9221,7 +9201,7 @@ describe('lib/optimizely', function() { afterEach(function() { eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); - errorHandler.handleError.restore(); + createdLogger.debug.restore(); createdLogger.info.restore(); createdLogger.warn.restore(); @@ -9255,7 +9235,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9292,7 +9272,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9330,14 +9310,14 @@ describe('lib/optimizely', function() { logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); var eventDispatcher = getMockEventDispatcher(); var eventProcessor = getForwardingEventProcessor( eventDispatcher ); beforeEach(function() { - sinon.stub(errorHandler, 'handleError'); + sinon.stub(createdLogger, 'debug'); sinon.stub(createdLogger, 'info'); sinon.stub(createdLogger, 'warn'); @@ -9350,7 +9330,7 @@ describe('lib/optimizely', function() { createdLogger.warn.restore(); createdLogger.error.restore(); eventDispatcher.dispatchEvent.reset(); - errorHandler.handleError.restore(); + }); var optlyInstance; @@ -9361,7 +9341,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, + projectConfigManager, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, @@ -9380,7 +9360,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: getMockProjectConfigManager(), - errorHandler: errorHandler, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9434,7 +9413,7 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager, - errorHandler: errorHandler, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9451,9 +9430,7 @@ describe('lib/optimizely', function() { const projectConfigManager = getMockProjectConfigManager({ onRunning: new Promise(function() {}) }); optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - errorHandler: errorHandler, - projectConfigManager, + clientEngine: 'node-sdk', projectConfigManager, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9476,7 +9453,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, projectConfigManager, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, @@ -9501,7 +9477,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, projectConfigManager, eventProcessor, jsonSchemaValidator: jsonSchemaValidator, @@ -9523,7 +9498,6 @@ describe('lib/optimizely', function() { it('can be called several times with different timeout values and the returned promises behave correctly', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, projectConfigManager: getMockProjectConfigManager(), eventProcessor, jsonSchemaValidator: jsonSchemaValidator, @@ -9553,7 +9527,6 @@ describe('lib/optimizely', function() { it('clears the timeout when the project config manager ready promise fulfills', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, projectConfigManager: getMockProjectConfigManager(), eventProcessor, jsonSchemaValidator: jsonSchemaValidator, @@ -9579,7 +9552,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, eventProcessor, projectConfigManager: fakeProjectConfigManager, jsonSchemaValidator: jsonSchemaValidator, @@ -9659,8 +9631,7 @@ describe('lib/optimizely', function() { var bucketStub; var fakeDecisionResponse; var eventDispatcherSpy; - var logger =createLogger(); - var errorHandler = { handleError: function() {} }; + var logger = createLogger(); var notificationCenter = createNotificationCenter({ logger }); var eventProcessor; beforeEach(function() { @@ -9677,7 +9648,6 @@ describe('lib/optimizely', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler, logger, isValidInstance: true, eventBatchSize: 1, diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index a71be212e..d8f4cdf09 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -20,7 +20,6 @@ import { NOTIFICATION_TYPES } from '../notification_center/type'; import OptimizelyUserContext from './'; import { createNotificationCenter } from '../notification_center'; import Optimizely from '../optimizely'; -import errorHandler from '../plugins/error_handler'; import { CONTROL_ATTRIBUTES, LOG_LEVEL } from '../utils/enums'; import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; @@ -61,7 +60,6 @@ const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { const optlyInstance = new Optimizely({ clientEngine: 'node-sdk', projectConfigManager: mockConfigManager, - errorHandler: errorHandler, eventProcessor, logger: createdLogger, isValidInstance: true, @@ -391,7 +389,7 @@ describe('lib/optimizely_user_context', function() { describe('when valid forced decision is set', function() { var optlyInstance; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); var eventDispatcher = getMockEventDispatcher(); var eventProcessor = getForwardingEventProcessor( eventDispatcher, @@ -402,7 +400,6 @@ describe('lib/optimizely_user_context', function() { projectConfigManager: getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) }), - errorHandler: errorHandler, eventProcessor, isValidInstance: true, logger: createdLogger, @@ -745,7 +742,7 @@ describe('lib/optimizely_user_context', function() { describe('when invalid forced decision is set', function() { var optlyInstance; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); var eventDispatcher = getMockEventDispatcher(); var eventProcessor = getForwardingEventProcessor( eventDispatcher, @@ -756,7 +753,6 @@ describe('lib/optimizely_user_context', function() { projectConfigManager: getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) }), - errorHandler: errorHandler, eventProcessor, isValidInstance: true, logger: createdLogger, @@ -852,7 +848,7 @@ describe('lib/optimizely_user_context', function() { logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); var eventDispatcher = getMockEventDispatcher(); var eventProcessor = getForwardingEventProcessor( eventDispatcher, @@ -863,7 +859,6 @@ describe('lib/optimizely_user_context', function() { projectConfigManager: getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) }), - errorHandler: errorHandler, eventProcessor, isValidInstance: true, logger: createdLogger, @@ -900,7 +895,7 @@ describe('lib/optimizely_user_context', function() { logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); var eventDispatcher = getMockEventDispatcher(); var eventProcessor = getForwardingEventProcessor( eventDispatcher, @@ -910,7 +905,6 @@ describe('lib/optimizely_user_context', function() { projectConfigManager: getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) }), - errorHandler: errorHandler, eventProcessor, isValidInstance: true, logger: createdLogger, diff --git a/lib/plugins/error_handler/index.tests.js b/lib/plugins/error_handler/index.tests.js deleted file mode 100644 index b3a632b92..000000000 --- a/lib/plugins/error_handler/index.tests.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2016, 2020 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; - -import { handleError } from './'; - -describe('lib/plugins/error_handler', function() { - describe('APIs', function() { - describe('handleError', function() { - it('should just be a no-op function', function() { - assert.isFunction(handleError); - }); - }); - }); -}); diff --git a/lib/plugins/error_handler/index.ts b/lib/plugins/error_handler/index.ts deleted file mode 100644 index 7afb8c5e3..000000000 --- a/lib/plugins/error_handler/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2016, 2020-2021, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Default error handler implementation - */ -export function handleError(): void { - // no-op -} - -export default { - handleError, -} diff --git a/lib/plugins/logger/index.react_native.tests.js b/lib/plugins/logger/index.react_native.tests.js deleted file mode 100644 index ad18ddad4..000000000 --- a/lib/plugins/logger/index.react_native.tests.js +++ /dev/null @@ -1,82 +0,0 @@ -// /** -// * Copyright 2019-2020 Optimizely -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -// import sinon from 'sinon'; -// import { assert } from 'chai'; - -// import { createLogger } from './index.react_native'; -// import { LOG_LEVEL } from '../../utils/enums'; - -// describe('lib/plugins/logger/react_native', function() { -// describe('APIs', function() { -// var defaultLogger; -// describe('createLogger', function() { -// it('should return an instance of the default logger', function() { -// defaultLogger = createLogger(); -// assert.isObject(defaultLogger); -// }); -// }); - -// describe('log', function() { -// beforeEach(function() { -// defaultLogger = createLogger(); - -// sinon.stub(console, 'log'); -// sinon.stub(console, 'info'); -// sinon.stub(console, 'warn'); -// sinon.stub(console, 'error'); -// }); - -// afterEach(function() { -// console.log.restore(); -// console.info.restore(); -// console.warn.restore(); -// console.error.restore(); -// }); - -// it('should use console.info when log level is info', function() { -// defaultLogger.log(LOG_LEVEL.INFO, 'message'); -// sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); -// sinon.assert.notCalled(console.log); -// sinon.assert.notCalled(console.warn); -// sinon.assert.notCalled(console.error); -// }); - -// it('should use console.log when log level is debug', function() { -// defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); -// sinon.assert.calledWithExactly(console.log, sinon.match(/.*DEBUG.*message.*/)); -// sinon.assert.notCalled(console.info); -// sinon.assert.notCalled(console.warn); -// sinon.assert.notCalled(console.error); -// }); - -// it('should use console.warn when log level is warn', function() { -// defaultLogger.log(LOG_LEVEL.WARNING, 'message'); -// sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARNING.*message.*/)); -// sinon.assert.notCalled(console.log); -// sinon.assert.notCalled(console.info); -// sinon.assert.notCalled(console.error); -// }); - -// it('should use console.warn when log level is error', function() { -// defaultLogger.log(LOG_LEVEL.ERROR, 'message'); -// sinon.assert.calledWithExactly(console.warn, sinon.match(/.*ERROR.*message.*/)); -// sinon.assert.notCalled(console.log); -// sinon.assert.notCalled(console.info); -// sinon.assert.notCalled(console.error); -// }); -// }); -// }); -// }); diff --git a/lib/plugins/logger/index.react_native.ts b/lib/plugins/logger/index.react_native.ts deleted file mode 100644 index 816944a15..000000000 --- a/lib/plugins/logger/index.react_native.ts +++ /dev/null @@ -1,60 +0,0 @@ -// /** -// * Copyright 2019-2022, Optimizely -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -// import { LogLevel } from '../../modules/logging'; -// import { sprintf } from '../../utils/fns'; -// import { NoOpLogger } from './index'; - -// function getLogLevelName(level: number): string { -// switch (level) { -// case LogLevel.INFO: -// return 'INFO'; -// case LogLevel.ERROR: -// return 'ERROR'; -// case LogLevel.WARNING: -// return 'WARNING'; -// case LogLevel.DEBUG: -// return 'DEBUG'; -// default: -// return 'NOTSET'; -// } -// } - -// class ReactNativeLogger { -// log(level: number, message: string): void { -// const formattedMessage = sprintf('[OPTIMIZELY] - %s %s %s', getLogLevelName(level), new Date().toISOString(), message); -// switch (level) { -// case LogLevel.INFO: -// console.info(formattedMessage); -// break; -// case LogLevel.ERROR: -// case LogLevel.WARNING: -// console.warn(formattedMessage); -// break; -// case LogLevel.DEBUG: -// case LogLevel.NOTSET: -// console.log(formattedMessage); -// break; -// } -// } -// } - -// export function createLogger(): ReactNativeLogger { -// return new ReactNativeLogger(); -// } - -// export function createNoOpLogger(): NoOpLogger { -// return new NoOpLogger(); -// } diff --git a/lib/plugins/logger/index.tests.js b/lib/plugins/logger/index.tests.js deleted file mode 100644 index cf153a2f0..000000000 --- a/lib/plugins/logger/index.tests.js +++ /dev/null @@ -1,112 +0,0 @@ -// /** -// * Copyright 2016, 2020, Optimizely -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -// import { assert, expect } from 'chai'; -// import sinon from 'sinon'; - -// import { createLogger } from './'; -// import { LOG_LEVEL } from '../../utils/enums';; - -// describe('lib/plugins/logger', function() { -// describe('APIs', function() { -// var defaultLogger; -// describe('createLogger', function() { -// it('should return an instance of the default logger', function() { -// defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); -// assert.isObject(defaultLogger); -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); -// }); -// }); - -// describe('log', function() { -// beforeEach(function() { -// defaultLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - -// sinon.stub(console, 'log'); -// sinon.stub(console, 'info'); -// sinon.stub(console, 'warn'); -// sinon.stub(console, 'error'); -// }); - -// afterEach(function() { -// console.log.restore(); -// console.info.restore(); -// console.warn.restore(); -// console.error.restore(); -// }); - -// it('should log a message at the threshold log level', function() { -// defaultLogger.log(LOG_LEVEL.INFO, 'message'); - -// sinon.assert.notCalled(console.log); -// sinon.assert.calledOnce(console.info); -// sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); -// sinon.assert.notCalled(console.warn); -// sinon.assert.notCalled(console.error); -// }); - -// it('should log a message if its log level is higher than the threshold log level', function() { -// defaultLogger.log(LOG_LEVEL.WARNING, 'message'); - -// sinon.assert.notCalled(console.log); -// sinon.assert.notCalled(console.info); -// sinon.assert.calledOnce(console.warn); -// sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARN.*message.*/)); -// sinon.assert.notCalled(console.error); -// }); - -// it('should not log a message if its log level is lower than the threshold log level', function() { -// defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); - -// sinon.assert.notCalled(console.log); -// sinon.assert.notCalled(console.info); -// sinon.assert.notCalled(console.warn); -// sinon.assert.notCalled(console.error); -// }); -// }); - -// describe('setLogLevel', function() { -// beforeEach(function() { -// defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); -// }); - -// it('should set the log level to the specified log level', function() { -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); - -// defaultLogger.setLogLevel(LOG_LEVEL.DEBUG); -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.DEBUG); - -// defaultLogger.setLogLevel(LOG_LEVEL.INFO); -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.INFO); -// }); - -// it('should set the log level to the ERROR when log level is not specified', function() { -// defaultLogger.setLogLevel(); -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); -// }); - -// it('should set the log level to the ERROR when log level is not valid', function() { -// defaultLogger.setLogLevel(-123); -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - -// defaultLogger.setLogLevel(undefined); -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - -// defaultLogger.setLogLevel('abc'); -// expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); -// }); -// }); -// }); -// }); From 493b1812d049fd8a305f2e10a738626207576390 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 28 Jan 2025 21:24:03 +0600 Subject: [PATCH 117/200] [FSSDK-11090] remove unnecessary console.log (#992) --- lib/optimizely/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 351b538d7..3747ba9a2 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -15,7 +15,7 @@ */ import { LoggerFacade } from '../logging/logger'; import { sprintf, objectValues } from '../utils/fns'; -import { createNotificationCenter, DefaultNotificationCenter, NotificationCenter } from '../notification_center'; +import { createNotificationCenter, DefaultNotificationCenter } from '../notification_center'; import { EventProcessor } from '../event_processor/event_processor'; import { OdpManager } from '../odp/odp_manager'; @@ -42,7 +42,7 @@ import OptimizelyUserContext from '../optimizely_user_context'; import { ProjectConfigManager } from '../project_config/project_config_manager'; import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; import { buildLogEvent } from '../event_processor/event_builder/log_event'; -import { buildImpressionEvent, buildConversionEvent, ImpressionEvent } from '../event_processor/event_builder/user_event'; +import { buildImpressionEvent, buildConversionEvent } from '../event_processor/event_builder/user_event'; import fns from '../utils/fns'; import { validate } from '../utils/attributes_validator'; import * as eventTagsValidator from '../utils/event_tags_validator'; @@ -388,10 +388,8 @@ export default class Optimizely implements Client { return; } - console.log(eventKey, userId, attributes, eventTags); if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { - console.log('eventKey not found',); this.logger?.warn(EVENT_KEY_NOT_FOUND, eventKey); this.logger?.warn(NOT_TRACKING_USER, userId); return; From fd9c9ed77cf4abb916bddfc1027af793c430f8b0 Mon Sep 17 00:00:00 2001 From: esrakartalOpt <102107327+esrakartalOpt@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:20:19 -0600 Subject: [PATCH 118/200] [FSSDK-11095] rewrite condition_tree_evaluator tests in Typescript (#994) * [FSSDK-11095] rewrite condition_tree_evaluator tests in Typescript * Remove only tag * Implement comments --- .../condition_tree_evaluator/index.spec.ts | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 lib/core/condition_tree_evaluator/index.spec.ts diff --git a/lib/core/condition_tree_evaluator/index.spec.ts b/lib/core/condition_tree_evaluator/index.spec.ts new file mode 100644 index 000000000..5afdd0d7d --- /dev/null +++ b/lib/core/condition_tree_evaluator/index.spec.ts @@ -0,0 +1,218 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, vi, expect } from 'vitest'; + +import * as conditionTreeEvaluator from '.'; + +const conditionA = { + name: 'browser_type', + value: 'safari', + type: 'custom_attribute', +}; +const conditionB = { + name: 'device_model', + value: 'iphone6', + type: 'custom_attribute', +}; +const conditionC = { + name: 'location', + match: 'exact', + type: 'custom_attribute', + value: 'CA', +}; +describe('evaluate', function() { + it('should return true for a leaf condition when the leaf condition evaluator returns true', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return true; + }) + ).toBe(true); + }); + + it('should return false for a leaf condition when the leaf condition evaluator returns false', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return false; + }) + ).toBe(false); + }); + + describe('and evaluation', function() { + it('should return true when ALL conditions evaluate to true', function() { + expect( + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return true; + }) + ).toBe(true); + }); + + it('should return false if one condition evaluates to false', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { + expect( + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return null when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBeNull(); + }); + + it('should return false when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => null).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + it('should return false when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => false) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB, conditionC], leafEvaluator)).toBe(false); + }); + }); + }); + + describe('or evaluation', function() { + it('should return true if any condition evaluates to true', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => true); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); + }); + + it('should return false if all conditions evaluate to false', function() { + expect( + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { + expect( + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return true when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); + }); + + it('should return null when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => null).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); + }); + + it('should return true when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => null) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB, conditionC], leafEvaluator)).toBe(true); + }); + }); + }); + + describe('not evaluation', function() { + it('should return true if the condition evaluates to false', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return false; + }) + ).toBe(true); + }); + + it('should return false if the condition evaluates to true', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionB], function() { + return true; + }) + ).toBe(false); + }); + + it('should return the result of negating the first condition, and ignore any additional conditions', function() { + let result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id: string) { + return id === '1'; + }); + expect(result).toBe(false); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id: string) { + return id === '2'; + }); + expect(result).toBe(true); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '3'], function(id: string) { + return id === '1' ? null : id === '3'; + }); + expect(result).toBeNull(); + }); + + describe('null handling', function() { + it('should return null when operand evaluates to null', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return null when there are no operands', function() { + expect( + conditionTreeEvaluator.evaluate(['not'], function() { + return null; + }) + ).toBeNull(); + }); + }); + }); + + describe('implicit operator', function() { + it('should behave like an "or" operator when the first item in the array is not a recognized operator', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate([conditionA, conditionB], leafEvaluator)).toBe(true); + expect( + conditionTreeEvaluator.evaluate([conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + }); +}); From 25e81e2b05ee00b3c9fc10d42864c2cac1d91f0f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 30 Jan 2025 22:02:25 +0600 Subject: [PATCH 119/200] [FSSDK-11101] cleanup OptiizelyOptions type (#997) --- lib/export_types.ts | 1 - lib/index.browser.ts | 4 ++-- lib/optimizely/index.ts | 22 +++++++++++++++++++++- lib/shared_types.ts | 26 -------------------------- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/lib/export_types.ts b/lib/export_types.ts index a55f56f27..84bda50c7 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -33,7 +33,6 @@ export { Event, EventDispatcher, DatafileOptions, - OptimizelyOptions, UserProfileService, UserProfile, ListenerPayload, diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 848524f48..bdb10fe42 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -18,7 +18,7 @@ import configValidator from './utils/config_validator'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; -import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; +import { OptimizelyDecideOption, Client, Config } from './shared_types'; import Optimizely from './optimizely'; import { UserAgentParser } from './odp/ua_parser/user_agent_parser'; import { getUserAgentParser } from './odp/ua_parser/ua_parser.browser'; @@ -57,7 +57,7 @@ const createInstance = function(config: Config): Client | null { logger = config.logger ? extractLogger(config.logger) : undefined; const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; - const optimizelyOptions: OptimizelyOptions = { + const optimizelyOptions = { ...config, clientEngine: clientEngine || enums.JAVASCRIPT_CLIENT_ENGINE, clientVersion: clientVersion || enums.CLIENT_VERSION, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 3747ba9a2..e7cb921de 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -31,7 +31,6 @@ import { Variation, FeatureFlag, FeatureVariable, - OptimizelyOptions, OptimizelyDecideOption, FeatureVariableValue, OptimizelyDecision, @@ -111,6 +110,27 @@ type StringInputs = Partial<Record<InputKey, unknown>>; type DecisionReasons = (string | number)[]; +/** + * options required to create optimizely object + */ +export type OptimizelyOptions = { + projectConfigManager: ProjectConfigManager; + UNSTABLE_conditionEvaluators?: unknown; + clientEngine: string; + clientVersion?: string; + errorNotifier?: ErrorNotifier; + eventProcessor?: EventProcessor; + jsonSchemaValidator?: { + validate(jsonObject: unknown): boolean; + }; + logger?: LoggerFacade; + userProfileService?: UserProfileService | null; + defaultDecideOptions?: OptimizelyDecideOption[]; + odpManager?: OdpManager; + vuidManager?: VuidManager + disposable?: boolean; +} + export default class Optimizely implements Client { private disposeOnUpdate?: Fn; private readyPromise: Promise<unknown>; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 725d84090..fe62e6471 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -248,32 +248,6 @@ export enum OptimizelyDecideOption { EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES', } -/** - * options required to create optimizely object - */ -export interface OptimizelyOptions { - projectConfigManager: ProjectConfigManager; - UNSTABLE_conditionEvaluators?: unknown; - clientEngine: string; - clientVersion?: string; - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object; - datafileManager?: DatafileManager; - errorNotifier?: ErrorNotifier; - eventProcessor?: EventProcessor; - jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean; - }; - logger?: LoggerFacade; - sdkKey?: string; - userProfileService?: UserProfileService | null; - defaultDecideOptions?: OptimizelyDecideOption[]; - odpManager?: OdpManager; - vuidManager?: VuidManager - disposable?: boolean; -} - /** * Optimizely Config Entities */ From bc49e3c1bbb3c053322db4a292311296a242f730 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 3 Feb 2025 22:32:17 +0600 Subject: [PATCH 120/200] [FSSDK-11101] refactor handling of no config availability (#998) --- lib/message/error_message.ts | 2 +- lib/optimizely/index.ts | 133 ++++++++++++++--------------------- 2 files changed, 52 insertions(+), 83 deletions(-) diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index 0dcb28567..5b12a5f68 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -71,7 +71,7 @@ export const UNKNOWN_CONDITION_TYPE = export const UNKNOWN_MATCH_TYPE = 'Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.'; export const UNRECOGNIZED_DECIDE_OPTION = 'Unrecognized decide option %s provided.'; -export const INVALID_OBJECT = 'Optimizely object is not valid. Failing %s.'; +export const NO_PROJECT_CONFIG_FAILURE = 'No project config available. Failing %s.'; export const EVENT_KEY_NOT_FOUND = 'Event key %s is not in datafile.'; export const NOT_TRACKING_USER = 'Not tracking user %s.'; export const VARIABLE_REQUESTED_WITH_WRONG_TYPE = diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index e7cb921de..416b4f3c8 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -22,6 +22,7 @@ import { OdpManager } from '../odp/odp_manager'; import { VuidManager } from '../vuid/vuid_manager'; import { OdpEvent } from '../odp/event_manager/odp_event'; import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; +import { BaseService } from '../service'; import { UserAttributes, @@ -71,7 +72,7 @@ import { ODP_EVENT_FAILED_ODP_MANAGER_MISSING, UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE, UNRECOGNIZED_DECIDE_OPTION, - INVALID_OBJECT, + NO_PROJECT_CONFIG_FAILURE, EVENT_KEY_NOT_FOUND, NOT_TRACKING_USER, VARIABLE_REQUESTED_WITH_WRONG_TYPE, @@ -265,16 +266,6 @@ export default class Optimizely implements Client { return this.projectConfigManager.getConfig() || null; } - /** - * Returns a truthy value if this instance currently has a valid project config - * object, and the initial configuration object that was passed into the - * constructor was also valid. - * @return {boolean} - */ - isValidInstance(): boolean { - return !!this.projectConfigManager.getConfig(); - } - /** * Buckets visitor and sends impression event to Optimizely. * @param {string} experimentKey @@ -284,8 +275,9 @@ export default class Optimizely implements Client { */ activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'activate'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'activate'); return null; } @@ -293,11 +285,6 @@ export default class Optimizely implements Client { return this.notActivatingExperiment(experimentKey, userId); } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - try { const variationKey = this.getVariation(experimentKey, userId, attributes); if (variationKey === null) { @@ -353,7 +340,7 @@ export default class Optimizely implements Client { return; } - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return; } @@ -394,8 +381,9 @@ export default class Optimizely implements Client { return; } - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'track'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'track'); return; } @@ -403,11 +391,6 @@ export default class Optimizely implements Client { return; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { this.logger?.warn(EVENT_KEY_NOT_FOUND, eventKey); @@ -453,8 +436,9 @@ export default class Optimizely implements Client { */ getVariation(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getVariation'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getVariation'); return null; } @@ -463,11 +447,6 @@ export default class Optimizely implements Client { return null; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - const experiment = configObj.experimentKeyMap[experimentKey]; if (!experiment || experiment.isRollout) { this.logger?.debug(INVALID_EXPERIMENT_KEY_INFO, experimentKey); @@ -517,7 +496,7 @@ export default class Optimizely implements Client { return false; } - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return false; } @@ -541,7 +520,7 @@ export default class Optimizely implements Client { return null; } - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return null; } @@ -624,8 +603,9 @@ export default class Optimizely implements Client { */ isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'isFeatureEnabled'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'isFeatureEnabled'); return false; } @@ -633,11 +613,6 @@ export default class Optimizely implements Client { return false; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return false; - } - const feature = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); if (!feature) { return false; @@ -704,17 +679,14 @@ export default class Optimizely implements Client { getEnabledFeatures(userId: string, attributes?: UserAttributes): string[] { try { const enabledFeatures: string[] = []; - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getEnabledFeatures'); - return enabledFeatures; - } - if (!this.validateInputs({ user_id: userId })) { + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getEnabledFeatures'); return enabledFeatures; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { + if (!this.validateInputs({ user_id: userId })) { return enabledFeatures; } @@ -752,8 +724,8 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): FeatureVariableValue { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getFeatureVariable'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariable'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, null, userId, attributes); @@ -796,7 +768,7 @@ export default class Optimizely implements Client { return null; } - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return null; } @@ -882,7 +854,7 @@ export default class Optimizely implements Client { variable: FeatureVariable, userId: string ): FeatureVariableValue { - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return null; } @@ -946,8 +918,8 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): boolean | null { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getFeatureVariableBoolean'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableBoolean'); return null; } return this.getFeatureVariableForType( @@ -984,8 +956,8 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): number | null { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getFeatureVariableDouble'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableDouble'); return null; } return this.getFeatureVariableForType( @@ -1022,8 +994,8 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): number | null { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getFeatureVariableInteger'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableInteger'); return null; } return this.getFeatureVariableForType( @@ -1060,8 +1032,8 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): string | null { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getFeatureVariableString'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableString'); return null; } return this.getFeatureVariableForType( @@ -1093,8 +1065,8 @@ export default class Optimizely implements Client { */ getFeatureVariableJSON(featureKey: string, variableKey: string, userId: string, attributes: UserAttributes): unknown { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getFeatureVariableJSON'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableJSON'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); @@ -1120,17 +1092,14 @@ export default class Optimizely implements Client { attributes?: UserAttributes ): { [variableKey: string]: unknown } | null { try { - if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, 'getAllFeatureVariables'); - return null; - } + const configObj = this.getProjectConfig(); - if (!this.validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getAllFeatureVariables'); return null; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { + if (!this.validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { return null; } @@ -1224,7 +1193,7 @@ export default class Optimizely implements Client { */ getOptimizelyConfig(): OptimizelyConfig | null { try { - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return null; } @@ -1423,10 +1392,10 @@ export default class Optimizely implements Client { } decide(user: OptimizelyUserContext, key: string, options: OptimizelyDecideOption[] = []): OptimizelyDecision { - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); - if (!this.isValidInstance() || !configObj) { - this.logger?.error(INVALID_OBJECT, 'decide'); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decide'); return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); } @@ -1568,10 +1537,10 @@ export default class Optimizely implements Client { const flagsWithoutForcedDecision = []; const validKeys = []; - const configObj = this.projectConfigManager.getConfig() + const configObj = this.getProjectConfig() - if (!this.isValidInstance() || !configObj) { - this.logger?.error(INVALID_OBJECT, 'decideForKeys'); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideForKeys'); return decisionMap; } if (keys.length === 0) { @@ -1638,10 +1607,10 @@ export default class Optimizely implements Client { user: OptimizelyUserContext, options: OptimizelyDecideOption[] = [] ): { [key: string]: OptimizelyDecision } { - const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; - if (!this.isValidInstance() || !configObj) { - this.logger?.error(INVALID_OBJECT, 'decideAll'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideAll'); return decisionMap; } @@ -1654,7 +1623,7 @@ export default class Optimizely implements Client { * Updates ODP Config with most recent ODP key, host, pixelUrl, and segments from the project config */ private updateOdpSettings(): void { - const projectConfig = this.projectConfigManager.getConfig(); + const projectConfig = this.getProjectConfig(); if (!projectConfig) { return; @@ -1696,7 +1665,7 @@ export default class Optimizely implements Client { * @returns { boolean } `true` if ODP settings were found in the datafile otherwise `false` */ public isOdpIntegrated(): boolean { - return this.projectConfigManager.getConfig()?.odpIntegrationConfig?.integrated ?? false; + return this.getProjectConfig()?.odpIntegrationConfig?.integrated ?? false; } /** From 21522ca8eed76ce9c5ae6ddeaf1f82bceb271ffd Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 5 Feb 2025 19:01:03 +0600 Subject: [PATCH 121/200] [FSSDK-11113] make Optimizely class instance of Service interface (#999) --- .../forwarding_event_processor.ts | 4 +- lib/message/error_message.ts | 2 +- lib/optimizely/index.tests.js | 57 +++-- lib/optimizely/index.ts | 200 ++++++++---------- lib/optimizely_user_context/index.ts | 18 +- lib/service.ts | 2 +- lib/shared_types.ts | 2 +- lib/utils/fns/index.ts | 2 +- 8 files changed, 125 insertions(+), 162 deletions(-) diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index d516afe7c..67899bddb 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -23,7 +23,7 @@ import { buildLogEvent } from './event_builder/log_event'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; -import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from 'error_message'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from 'error_message'; import { OptimizelyError } from '../error/optimizly_error'; class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; @@ -56,7 +56,7 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new OptimizelyError(SERVICE_STOPPED_BEFORE_IT_WAS_STARTED)); + this.startPromise.reject(new OptimizelyError(SERVICE_STOPPED_BEFORE_RUNNING)); } this.state = ServiceState.Terminated; diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index 5b12a5f68..66bf469f7 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -95,7 +95,7 @@ export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it coul export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; export const NO_SDKKEY_OR_DATAFILE = 'At least one of sdkKey or datafile must be provided'; export const RETRY_CANCELLED = 'Retry cancelled'; -export const SERVICE_STOPPED_BEFORE_IT_WAS_STARTED = 'Service stopped before it was started'; +export const SERVICE_STOPPED_BEFORE_RUNNING = 'Service stopped before running'; export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; export const SEND_BEACON_FAILED = 'sendBeacon failed'; export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index f2a739a04..c4efb2c67 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -37,26 +37,15 @@ import { FEATURE_NOT_ENABLED_FOR_USER, INVALID_CLIENT_ENGINE, INVALID_DEFAULT_DECIDE_OPTIONS, - INVALID_OBJECT, NOT_ACTIVATING_USER, - USER_HAS_NO_FORCED_VARIATION, - USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - USER_MAPPED_TO_FORCED_VARIATION, - USER_RECEIVED_DEFAULT_VARIABLE_VALUE, VALID_USER_PROFILE_SERVICE, - VARIATION_REMOVED_FOR_USER, } from 'log_message'; import { - EXPERIMENT_KEY_NOT_IN_DATAFILE, - INVALID_ATTRIBUTES, NOT_TRACKING_USER, EVENT_KEY_NOT_FOUND, INVALID_EXPERIMENT_KEY, - INVALID_INPUT_FORMAT, - NO_VARIATION_FOR_EXPERIMENT_KEY, - USER_NOT_IN_FORCED_VARIATION, - INSTANCE_CLOSED, ONREADY_TIMEOUT, + SERVICE_STOPPED_BEFORE_RUNNING } from 'error_message'; import { @@ -77,6 +66,7 @@ import { } from '../core/decision_service'; import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP } from '../core/bucketer'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; var LOG_LEVEL = enums.LOG_LEVEL; var DECISION_SOURCES = enums.DECISION_SOURCES; @@ -9253,10 +9243,10 @@ describe('lib/optimizely', function() { }); }); - it('returns a promise that fulfills with a successful result object', function() { - return optlyInstance.close().then(function(result) { - assert.deepEqual(result, { success: true }); - }); + it('returns a promise that resolves', function() { + return optlyInstance.close().then().catch(() => { + assert.fail(); + }) }); }); @@ -9291,13 +9281,11 @@ describe('lib/optimizely', function() { }); }); - it('returns a promise that fulfills with an unsuccessful result object', function() { - return optlyInstance.close().then(function(result) { - // assert.deepEqual(result, { - // success: false, - // reason: 'Error: Failed to stop', - // }); - assert.isFalse(result.success); + it('returns a promise that rejects', function() { + return optlyInstance.close().then(() => { + assert.fail('promnise should reject') + }).catch(() => { + }); }); }); @@ -9465,7 +9453,7 @@ describe('lib/optimizely', function() { var readyPromise = optlyInstance.onReady(); clock.tick(300001); return readyPromise.then(() => { - return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); + return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); }, (err) => { assert.equal(err.baseMessage, ONREADY_TIMEOUT); assert.deepEqual(err.params, [ 30000 ]); @@ -9487,18 +9475,25 @@ describe('lib/optimizely', function() { eventProcessor, }); var readyPromise = optlyInstance.onReady({ timeout: 100 }); + optlyInstance.close(); + return readyPromise.then(() => { - return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); + return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); }, (err) => { - assert.equal(err.baseMessage, INSTANCE_CLOSED); + assert.equal(err.baseMessage, SERVICE_STOPPED_BEFORE_RUNNING); }); }); it('can be called several times with different timeout values and the returned promises behave correctly', function() { + const onRunning = resolvablePromise(); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - projectConfigManager: getMockProjectConfigManager(), + projectConfigManager: getMockProjectConfigManager({ + onRunning: onRunning.promise, + }), + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -9512,16 +9507,16 @@ describe('lib/optimizely', function() { var readyPromise3 = optlyInstance.onReady({ timeout: 300 }); clock.tick(101); return readyPromise1 - .then(function() { + .catch(function() { clock.tick(100); return readyPromise2; }) - .then(function() { + .catch(function() { // readyPromise3 has not resolved yet because only 201 ms have elapsed. // Calling close on the instance should resolve readyPromise3 - optlyInstance.close(); + optlyInstance.close().catch(() => {}); return readyPromise3; - }); + }).catch(() => {}); }); it('clears the timeout when the project config manager ready promise fulfills', function() { diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 416b4f3c8..9ca0c6089 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -22,7 +22,7 @@ import { OdpManager } from '../odp/odp_manager'; import { VuidManager } from '../vuid/vuid_manager'; import { OdpEvent } from '../odp/event_manager/odp_event'; import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; -import { BaseService } from '../service'; +import { BaseService, ServiceState } from '../service'; import { UserAttributes, @@ -43,7 +43,7 @@ import { ProjectConfigManager } from '../project_config/project_config_manager'; import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; import { buildLogEvent } from '../event_processor/event_builder/log_event'; import { buildImpressionEvent, buildConversionEvent } from '../event_processor/event_builder/user_event'; -import fns from '../utils/fns'; +import { isSafeInteger } from '../utils/fns'; import { validate } from '../utils/attributes_validator'; import * as eventTagsValidator from '../utils/event_tags_validator'; import * as projectConfig from '../project_config/project_config'; @@ -77,7 +77,8 @@ import { NOT_TRACKING_USER, VARIABLE_REQUESTED_WITH_WRONG_TYPE, ONREADY_TIMEOUT, - INSTANCE_CLOSED + INSTANCE_CLOSED, + SERVICE_STOPPED_BEFORE_RUNNING } from 'error_message'; import { @@ -132,18 +133,13 @@ export type OptimizelyOptions = { disposable?: boolean; } -export default class Optimizely implements Client { - private disposeOnUpdate?: Fn; - private readyPromise: Promise<unknown>; - // readyTimeout is specified as any to make this work in both browser & Node - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readyTimeouts: { [key: string]: { readyTimeout: any; onClose: () => void } }; - private nextReadyTimeoutId: number; +export default class Optimizely extends BaseService implements Client { + private cleanupTasks: Map<number, Fn> = new Map(); + private nextCleanupTaskId = 0; private clientEngine: string; private clientVersion: string; private errorNotifier?: ErrorNotifier; private errorReporter: ErrorReporter; - protected logger?: LoggerFacade; private projectConfigManager: ProjectConfigManager; private decisionService: DecisionService; private eventProcessor?: EventProcessor; @@ -153,6 +149,8 @@ export default class Optimizely implements Client { private vuidManager?: VuidManager; constructor(config: OptimizelyOptions) { + super(); + let clientEngine = config.clientEngine; if (!clientEngine) { config.logger?.info(INVALID_CLIENT_ENGINE, clientEngine); @@ -193,7 +191,8 @@ export default class Optimizely implements Client { }); this.defaultDecideOptions = defaultDecideOptions; - this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { + this.projectConfigManager = config.projectConfigManager; + this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { this.logger?.info( UPDATED_OPTIMIZELY_CONFIG, configObj.revision, @@ -205,9 +204,14 @@ export default class Optimizely implements Client { this.updateOdpSettings(); }); - this.projectConfigManager.start(); - const projectConfigManagerRunningPromise = this.projectConfigManager.onRunning(); + this.eventProcessor = config.eventProcessor; + this.eventProcessor?.onDispatch((event) => { + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, event); + }); + this.odpManager = config.odpManager; + + let userProfileService: UserProfileService | null = null; if (config.userProfileService) { try { @@ -228,34 +232,39 @@ export default class Optimizely implements Client { this.notificationCenter = createNotificationCenter({ logger: this.logger, errorNotifier: this.errorNotifier }); - this.eventProcessor = config.eventProcessor; + this.start(); + } - this.eventProcessor?.start(); - const eventProcessorRunningPromise = this.eventProcessor ? this.eventProcessor.onRunning() : - Promise.resolve(undefined); + start(): void { + if (!this.isNew()) { + return; + } - this.eventProcessor?.onDispatch((event) => { - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, event); - }); + super.start(); + this.state = ServiceState.Starting; + this.projectConfigManager.start(); + this.eventProcessor?.start(); this.odpManager?.start(); - this.readyPromise = Promise.all([ - projectConfigManagerRunningPromise, - eventProcessorRunningPromise, - config.odpManager ? config.odpManager.onRunning() : Promise.resolve(), - config.vuidManager ? config.vuidManager.initialize() : Promise.resolve(), - ]); + Promise.all([ + this.projectConfigManager.onRunning(), + this.eventProcessor ? this.eventProcessor.onRunning() : Promise.resolve(), + this.odpManager ? this.odpManager.onRunning() : Promise.resolve(), + this.vuidManager ? this.vuidManager.initialize() : Promise.resolve(), + ]).then(() => { + this.state = ServiceState.Running; + this.startPromise.resolve(); - this.readyPromise.then(() => { const vuid = this.vuidManager?.getVuid(); if (vuid) { this.odpManager?.setVuid(vuid); } + }).catch((err) => { + this.state = ServiceState.Failed; + this.errorReporter.report(err); + this.startPromise.reject(err); }); - - this.readyTimeouts = {}; - this.nextReadyTimeoutId = 0; } /** @@ -1220,62 +1229,47 @@ export default class Optimizely implements Client { * above) are complete. If there are no in-flight event dispatcher requests and * no queued events waiting to be sent, returns an immediately-fulfilled Promise. * - * Returned Promises are fulfilled with result objects containing these - * properties: - * - success (boolean): true if the event dispatcher signaled completion of - * all in-flight and final requests, or if there were no - * queued events and no in-flight requests. false if an - * unexpected error was encountered during the close - * process. - * - reason (string=): If success is false, this is a string property with - * an explanatory message. * * NOTE: After close is called, this instance is no longer usable - any events * generated will no longer be sent to the event dispatcher. * * @return {Promise} */ - close(): Promise<{ success: boolean; reason?: string }> { - try { - this.projectConfigManager.stop(); - this.eventProcessor?.stop(); - this.odpManager?.stop(); - this.notificationCenter.clearAllNotificationListeners(); - - const eventProcessorStoppedPromise = this.eventProcessor ? this.eventProcessor.onTerminated() : - Promise.resolve(); - - if (this.disposeOnUpdate) { - this.disposeOnUpdate(); - this.disposeOnUpdate = undefined; - } + close(): Promise<unknown> { + this.stop(); + return this.onTerminated(); + } - Object.keys(this.readyTimeouts).forEach((readyTimeoutId: string) => { - const readyTimeoutRecord = this.readyTimeouts[readyTimeoutId]; - clearTimeout(readyTimeoutRecord.readyTimeout); - readyTimeoutRecord.onClose(); - }); - this.readyTimeouts = {}; - return eventProcessorStoppedPromise.then( - function() { - return { - success: true, - }; - }, - function(err) { - return { - success: false, - reason: String(err), - }; - } - ); - } catch (err) { - this.errorReporter.report(err); - return Promise.resolve({ - success: false, - reason: String(err), - }); + stop(): void { + if (this.isDone()) { + return; } + + if (!this.isRunning()) { + this.startPromise.reject(new OptimizelyError(SERVICE_STOPPED_BEFORE_RUNNING)); + } + + this.state = ServiceState.Stopping; + + this.projectConfigManager.stop(); + this.eventProcessor?.stop(); + this.odpManager?.stop(); + this.notificationCenter.clearAllNotificationListeners(); + + this.cleanupTasks.forEach((onClose) => onClose()); + + Promise.all([ + this.projectConfigManager.onTerminated(), + this.eventProcessor ? this.eventProcessor.onTerminated() : Promise.resolve(), + this.odpManager ? this.odpManager.onTerminated() : Promise.resolve(), + ]).then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve() + }).catch((err) => { + this.errorReporter.report(err); + this.state = ServiceState.Failed; + this.stopPromise.reject(err); + }); } /** @@ -1312,35 +1306,30 @@ export default class Optimizely implements Client { timeoutValue = options.timeout; } } - if (!fns.isSafeInteger(timeoutValue)) { + if (!isSafeInteger(timeoutValue)) { timeoutValue = DEFAULT_ONREADY_TIMEOUT; } const timeoutPromise = resolvablePromise(); - const timeoutId = this.nextReadyTimeoutId++; + const cleanupTaskId = this.nextCleanupTaskId++; const onReadyTimeout = () => { - delete this.readyTimeouts[timeoutId]; + this.cleanupTasks.delete(cleanupTaskId); timeoutPromise.reject(new OptimizelyError(ONREADY_TIMEOUT, timeoutValue)); }; const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); - const onClose = function() { - timeoutPromise.reject(new OptimizelyError(INSTANCE_CLOSED)); - }; - - this.readyTimeouts[timeoutId] = { - readyTimeout: readyTimeout, - onClose: onClose, - }; - - this.readyPromise.then(() => { + + this.cleanupTasks.set(cleanupTaskId, () => { clearTimeout(readyTimeout); - delete this.readyTimeouts[timeoutId]; + timeoutPromise.reject(new OptimizelyError(INSTANCE_CLOSED)); }); - return Promise.race([this.readyPromise, timeoutPromise]); + return Promise.race([this.onRunning().then(() => { + clearTimeout(readyTimeout); + this.cleanupTasks.delete(cleanupTaskId); + }), timeoutPromise]); } //============ decide ============// @@ -1363,12 +1352,19 @@ export default class Optimizely implements Client { return null; } - return new OptimizelyUserContext({ + const userContext = new OptimizelyUserContext({ optimizely: this, userId: userIdentifier, attributes, - shouldIdentifyUser: true, }); + + this.onRunning().then(() => { + if (this.odpManager && this.isOdpIntegrated()) { + this.odpManager.identifyUser(userIdentifier); + } + }).catch(() => {}); + + return userContext; } /** @@ -1387,7 +1383,6 @@ export default class Optimizely implements Client { optimizely: this, userId, attributes, - shouldIdentifyUser: false, }); } @@ -1668,17 +1663,6 @@ export default class Optimizely implements Client { return this.getProjectConfig()?.odpIntegrationConfig?.integrated ?? false; } - /** - * Identifies user with ODP server in a fire-and-forget manner. - * Should be called only after the instance is ready - * @param {string} userId - */ - public identifyUser(userId: string): void { - if (this.odpManager && this.isOdpIntegrated()) { - this.odpManager.identifyUser(userId); - } - } - /** * Fetches list of qualified segments from ODP for a particular userId. * @param {string} userId diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index b2a524a5e..46fa103f4 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -30,7 +30,6 @@ interface OptimizelyUserContextConfig { optimizely: Optimizely; userId: string; attributes?: UserAttributes; - shouldIdentifyUser?: boolean; } export interface IOptimizelyUserContext { @@ -57,25 +56,11 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { private forcedDecisionsMap: { [key: string]: { [key: string]: OptimizelyForcedDecision } }; private _qualifiedSegments: string[] | null = null; - constructor({ optimizely, userId, attributes, shouldIdentifyUser = true }: OptimizelyUserContextConfig) { + constructor({ optimizely, userId, attributes }: OptimizelyUserContextConfig) { this.optimizely = optimizely; this.userId = userId; this.attributes = { ...attributes } ?? {}; this.forcedDecisionsMap = {}; - - if (shouldIdentifyUser) { - this.optimizely.onReady().then(() => { - this.identifyUser(); - }).catch(() => {}); - } - } - - /** - * On user context instantiation, fire event to attempt to identify user to ODP. - * Note: This fails if ODP is not enabled. - */ - private identifyUser(): void { - this.optimizely.identifyUser(this.userId); } /** @@ -235,7 +220,6 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { private cloneUserContext(): OptimizelyUserContext { const userContext = new OptimizelyUserContext({ - shouldIdentifyUser: false, optimizely: this.getOptimizely(), userId: this.getUserId(), attributes: this.getAttributes(), diff --git a/lib/service.ts b/lib/service.ts index 03e23ee67..b024ef510 100644 --- a/lib/service.ts +++ b/lib/service.ts @@ -66,7 +66,7 @@ export abstract class BaseService implements Service { this.startPromise = resolvablePromise(); this.stopPromise = resolvablePromise(); this.startupLogs = startupLogs; - + // avoid unhandled promise rejection this.startPromise.promise.catch(() => {}); this.stopPromise.promise.catch(() => {}); diff --git a/lib/shared_types.ts b/lib/shared_types.ts index fe62e6471..0cb41e6d0 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -319,7 +319,7 @@ export interface Client { ): { [variableKey: string]: unknown } | null; getOptimizelyConfig(): OptimizelyConfig | null; onReady(options?: { timeout?: number }): Promise<unknown>; - close(): Promise<{ success: boolean; reason?: string }>; + close(): Promise<unknown>; sendOdpEvent(action: string, type?: string, identifiers?: Map<string, string>, data?: Map<string, unknown>): void; getProjectConfig(): ProjectConfig | null; isOdpIntegrated(): boolean; diff --git a/lib/utils/fns/index.ts b/lib/utils/fns/index.ts index 3a427f5dc..7d9506756 100644 --- a/lib/utils/fns/index.ts +++ b/lib/utils/fns/index.ts @@ -21,7 +21,7 @@ export function currentTimestamp(): number { return Math.round(new Date().getTime()); } -function isSafeInteger(number: unknown): boolean { +export function isSafeInteger(number: unknown): boolean { return typeof number == 'number' && Math.abs(number) <= MAX_SAFE_INTEGER_LIMIT; } From 506d4417156860fbe430894b81333603975facda Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 7 Feb 2025 23:32:30 +0600 Subject: [PATCH 122/200] Junaed/fssdk 11034 tests js to ts (#1000) --- lib/project_config/optimizely_config.spec.ts | 942 +++++++++++++++ lib/project_config/project_config.spec.ts | 1132 ++++++++++++++++++ 2 files changed, 2074 insertions(+) create mode 100644 lib/project_config/optimizely_config.spec.ts create mode 100644 lib/project_config/project_config.spec.ts diff --git a/lib/project_config/optimizely_config.spec.ts b/lib/project_config/optimizely_config.spec.ts new file mode 100644 index 000000000..3e7288a8e --- /dev/null +++ b/lib/project_config/optimizely_config.spec.ts @@ -0,0 +1,942 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, vi, assert } from 'vitest'; +import { createOptimizelyConfig, OptimizelyConfig } from './optimizely_config'; +import { createProjectConfig, ProjectConfig } from './project_config'; +import { + getTestProjectConfigWithFeatures, + getTypedAudiencesConfig, + getSimilarRuleKeyConfig, + getSimilarExperimentKeyConfig, + getDuplicateExperimentKeyConfig, +} from '../tests/test_data'; +import { Experiment } from '../shared_types'; +import { LoggerFacade } from '../logging/logger'; + +const datafile: ProjectConfig = getTestProjectConfigWithFeatures(); +const typedAudienceDatafile = getTypedAudiencesConfig(); +const similarRuleKeyDatafile = getSimilarRuleKeyConfig(); +const similarExperimentKeyDatafile = getSimilarExperimentKeyConfig(); +const cloneDeep = (obj: any) => JSON.parse(JSON.stringify(obj)); +const getAllExperimentsFromDatafile = (datafile: ProjectConfig) => { + const allExperiments: Experiment[] = []; + datafile.groups.forEach(group => { + group.experiments.forEach(experiment => { + allExperiments.push(experiment); + }); + }); + datafile.experiments.forEach(experiment => { + allExperiments.push(experiment); + }); + return allExperiments; +}; + +describe('Optimizely Config', () => { + let optimizelyConfigObject: OptimizelyConfig; + let projectConfigObject: ProjectConfig; + let projectTypedAudienceConfigObject: ProjectConfig; + let optimizelySimilarRuleKeyConfigObject: OptimizelyConfig; + let projectSimilarRuleKeyConfigObject: ProjectConfig; + let optimizelySimilarExperimentkeyConfigObject: OptimizelyConfig; + let projectSimilarExperimentKeyConfigObject: ProjectConfig; + + const logger: LoggerFacade = { + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + child: vi.fn().mockReturnValue(this), + }; + + beforeEach(() => { + projectConfigObject = createProjectConfig(cloneDeep(datafile as any)); + optimizelyConfigObject = createOptimizelyConfig(projectConfigObject, JSON.stringify(datafile)); + projectTypedAudienceConfigObject = createProjectConfig(cloneDeep(typedAudienceDatafile)); + projectSimilarRuleKeyConfigObject = createProjectConfig(cloneDeep(similarRuleKeyDatafile)); + optimizelySimilarRuleKeyConfigObject = createOptimizelyConfig( + projectSimilarRuleKeyConfigObject, + JSON.stringify(similarRuleKeyDatafile) + ); + projectSimilarExperimentKeyConfigObject = createProjectConfig(cloneDeep(similarExperimentKeyDatafile)); + optimizelySimilarExperimentkeyConfigObject = createOptimizelyConfig( + projectSimilarExperimentKeyConfigObject, + JSON.stringify(similarExperimentKeyDatafile) + ); + }); + + it('should return all experiments except rollouts', () => { + const experimentsMap = optimizelyConfigObject.experimentsMap; + const experimentsCount = Object.keys(experimentsMap).length; + + expect(experimentsCount).toBe(12); + + const allExperiments: Experiment[] = getAllExperimentsFromDatafile(datafile); + + allExperiments.forEach(experiment => { + expect(experimentsMap[experiment.key]).toMatchObject({ + id: experiment.id, + key: experiment.key, + }); + + const variationsMap = experimentsMap[experiment.key].variationsMap; + + experiment.variations.forEach(variation => { + expect(variationsMap[variation.key]).toMatchObject({ + id: variation.id, + key: variation.key, + }); + }); + }); + }); + + it('should keep the last experiment in case of duplicate key and log a warning', () => { + const datafile = getDuplicateExperimentKeyConfig(); + const configObj = createProjectConfig(datafile, JSON.stringify(datafile)); + const optimizelyConfig = createOptimizelyConfig(configObj, JSON.stringify(datafile), logger); + const experimentsMap = optimizelyConfig.experimentsMap; + const duplicateKey = 'experiment_rule'; + const lastExperiment = datafile.experiments[datafile.experiments.length - 1]; + + expect(experimentsMap['experiment_rule'].id).toBe(lastExperiment.id); + expect(logger.warn).toHaveBeenCalledWith(`Duplicate experiment keys found in datafile: ${duplicateKey}`); + }); + + it('should return all the feature flags', function() { + const featureFlagsCount = Object.keys(optimizelyConfigObject.featuresMap).length; + assert.equal(featureFlagsCount, 9); + + const featuresMap = optimizelyConfigObject.featuresMap; + const expectedDeliveryRules = [ + [ + { + id: '594031', + key: '594031', + audiences: '', + variationsMap: { + '594032': { + id: '594032', + key: '594032', + featureEnabled: true, + variablesMap: { + new_content: { + id: '4919852825313280', + key: 'new_content', + type: 'boolean', + value: 'true', + }, + lasers: { + id: '5482802778734592', + key: 'lasers', + type: 'integer', + value: '395', + }, + price: { + id: '6045752732155904', + key: 'price', + type: 'double', + value: '4.99', + }, + message: { + id: '6327227708866560', + key: 'message', + type: 'string', + value: 'Hello audience', + }, + message_info: { + id: '8765345281230956', + key: 'message_info', + type: 'json', + value: '{ "count": 2, "message": "Hello audience" }', + }, + }, + }, + }, + }, + { + id: '594037', + key: '594037', + audiences: '', + variationsMap: { + '594038': { + id: '594038', + key: '594038', + featureEnabled: false, + variablesMap: { + new_content: { + id: '4919852825313280', + key: 'new_content', + type: 'boolean', + value: 'false', + }, + lasers: { + id: '5482802778734592', + key: 'lasers', + type: 'integer', + value: '400', + }, + price: { + id: '6045752732155904', + key: 'price', + type: 'double', + value: '14.99', + }, + message: { + id: '6327227708866560', + key: 'message', + type: 'string', + value: 'Hello', + }, + message_info: { + id: '8765345281230956', + key: 'message_info', + type: 'json', + value: '{ "count": 1, "message": "Hello" }', + }, + }, + }, + }, + }, + ], + [ + { + id: '594060', + key: '594060', + audiences: '', + variationsMap: { + '594061': { + id: '594061', + key: '594061', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '27.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is NOT coming', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '10003', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'false', + }, + }, + }, + }, + }, + { + id: '594066', + key: '594066', + audiences: '', + variationsMap: { + '594067': { + id: '594067', + key: '594067', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '30.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is coming definitely', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '500', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'true', + }, + }, + }, + }, + }, + ], + [], + [], + [ + { + id: '599056', + key: '599056', + audiences: '', + variationsMap: { + '599057': { + id: '599057', + key: '599057', + featureEnabled: true, + variablesMap: { + lasers: { + id: '4937719889264640', + key: 'lasers', + type: 'integer', + value: '200', + }, + message: { + id: '6345094772817920', + key: 'message', + type: 'string', + value: "i'm a rollout", + }, + }, + }, + }, + }, + ], + [], + [], + [ + { + id: '594060', + key: '594060', + audiences: '', + variationsMap: { + '594061': { + id: '594061', + key: '594061', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '27.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is NOT coming', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '10003', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'false', + }, + }, + }, + }, + }, + { + id: '594066', + key: '594066', + audiences: '', + variationsMap: { + '594067': { + id: '594067', + key: '594067', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '30.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is coming definitely', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '500', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'true', + }, + }, + }, + }, + }, + ], + [ + { + id: '594060', + key: '594060', + audiences: '', + variationsMap: { + '594061': { + id: '594061', + key: '594061', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '27.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is NOT coming', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '10003', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'false', + }, + }, + }, + }, + }, + { + id: '594066', + key: '594066', + audiences: '', + variationsMap: { + '594067': { + id: '594067', + key: '594067', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '30.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is coming definitely', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '500', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'true', + }, + }, + }, + }, + }, + ], + ]; + const expectedExperimentRules = [ + [], + [], + [ + { + id: '594098', + key: 'testing_my_feature', + audiences: '', + variationsMap: { + variation: { + id: '594096', + key: 'variation', + featureEnabled: true, + variablesMap: { + num_buttons: { + id: '4792309476491264', + key: 'num_buttons', + type: 'integer', + value: '2', + }, + is_button_animated: { + id: '5073784453201920', + key: 'is_button_animated', + type: 'boolean', + value: 'true', + }, + button_txt: { + id: '5636734406623232', + key: 'button_txt', + type: 'string', + value: 'Buy me NOW', + }, + button_width: { + id: '6199684360044544', + key: 'button_width', + type: 'double', + value: '20.25', + }, + button_info: { + id: '1547854156498475', + key: 'button_info', + type: 'json', + value: '{ "num_buttons": 1, "text": "first variation"}', + }, + }, + }, + control: { + id: '594097', + key: 'control', + featureEnabled: true, + variablesMap: { + num_buttons: { + id: '4792309476491264', + key: 'num_buttons', + type: 'integer', + value: '10', + }, + is_button_animated: { + id: '5073784453201920', + key: 'is_button_animated', + type: 'boolean', + value: 'false', + }, + button_txt: { + id: '5636734406623232', + key: 'button_txt', + type: 'string', + value: 'Buy me', + }, + button_width: { + id: '6199684360044544', + key: 'button_width', + type: 'double', + value: '50.55', + }, + button_info: { + id: '1547854156498475', + key: 'button_info', + type: 'json', + value: '{ "num_buttons": 2, "text": "second variation"}', + }, + }, + }, + variation2: { + id: '594099', + key: 'variation2', + featureEnabled: false, + variablesMap: { + num_buttons: { + id: '4792309476491264', + key: 'num_buttons', + type: 'integer', + value: '10', + }, + is_button_animated: { + id: '5073784453201920', + key: 'is_button_animated', + type: 'boolean', + value: 'false', + }, + button_txt: { + id: '5636734406623232', + key: 'button_txt', + type: 'string', + value: 'Buy me', + }, + button_width: { + id: '6199684360044544', + key: 'button_width', + type: 'double', + value: '50.55', + }, + button_info: { + id: '1547854156498475', + key: 'button_info', + type: 'json', + value: '{ "num_buttons": 0, "text": "default value"}', + }, + }, + }, + }, + }, + ], + [ + { + id: '595010', + key: 'exp_with_group', + audiences: '', + variationsMap: { + var: { + featureEnabled: undefined, + id: '595008', + key: 'var', + variablesMap: {}, + }, + con: { + featureEnabled: undefined, + id: '595009', + key: 'con', + variablesMap: {}, + }, + }, + }, + ], + [ + { + id: '599028', + key: 'test_shared_feature', + audiences: '', + variationsMap: { + treatment: { + id: '599026', + key: 'treatment', + featureEnabled: true, + variablesMap: { + lasers: { + id: '4937719889264640', + key: 'lasers', + type: 'integer', + value: '100', + }, + message: { + id: '6345094772817920', + key: 'message', + type: 'string', + value: 'shared', + }, + }, + }, + control: { + id: '599027', + key: 'control', + featureEnabled: false, + variablesMap: { + lasers: { + id: '4937719889264640', + key: 'lasers', + type: 'integer', + value: '100', + }, + message: { + id: '6345094772817920', + key: 'message', + type: 'string', + value: 'shared', + }, + }, + }, + }, + }, + ], + [], + [ + { + id: '12115595439', + key: 'no_traffic_experiment', + audiences: '', + variationsMap: { + variation_5000: { + featureEnabled: undefined, + id: '12098126629', + key: 'variation_5000', + variablesMap: {}, + }, + variation_10000: { + featureEnabled: undefined, + id: '12098126630', + key: 'variation_10000', + variablesMap: {}, + }, + }, + }, + ], + [ + { + id: '42222', + key: 'group_2_exp_1', + audiences: '"Test attribute users 3"', + variationsMap: { + var_1: { + id: '38901', + key: 'var_1', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '42223', + key: 'group_2_exp_2', + audiences: '"Test attribute users 3"', + variationsMap: { + var_1: { + id: '38905', + key: 'var_1', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '42224', + key: 'group_2_exp_3', + audiences: '"Test attribute users 3"', + variationsMap: { + var_1: { + id: '38906', + key: 'var_1', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + ], + [ + { + id: '111134', + key: 'test_experiment3', + audiences: '"Test attribute users 3"', + variationsMap: { + control: { + id: '222239', + key: 'control', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '111135', + key: 'test_experiment4', + audiences: '"Test attribute users 3"', + variationsMap: { + control: { + id: '222240', + key: 'control', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '111136', + key: 'test_experiment5', + audiences: '"Test attribute users 3"', + variationsMap: { + control: { + id: '222241', + key: 'control', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + ], + ]; + + datafile.featureFlags.forEach((featureFlag, index) => { + expect(featuresMap[featureFlag.key]).toMatchObject({ + id: featureFlag.id, + key: featureFlag.key, + }); + + featureFlag.experimentIds.forEach(experimentId => { + const experimentKey = projectConfigObject.experimentIdMap[experimentId].key; + + expect(!!featuresMap[featureFlag.key].experimentsMap[experimentKey]).toBe(true); + }); + + const variablesMap = featuresMap[featureFlag.key].variablesMap; + const deliveryRules = featuresMap[featureFlag.key].deliveryRules; + const experimentRules = featuresMap[featureFlag.key].experimentRules; + + expect(deliveryRules).toEqual(expectedDeliveryRules[index]); + expect(experimentRules).toEqual(expectedExperimentRules[index]); + + featureFlag.variables.forEach(variable => { + // json is represented as sub type of string to support backwards compatibility in datafile. + // project config treats it as a first-class type. + const expectedVariableType = variable.type === 'string' && variable.subType === 'json' ? 'json' : variable.type; + + expect(variablesMap[variable.key]).toMatchObject({ + id: variable.id, + key: variable.key, + type: expectedVariableType, + value: variable.defaultValue, + }); + }); + }); + }); + + it('should correctly merge all feature variables', () => { + const featureFlags = datafile.featureFlags; + const datafileExperimentsMap: Record<string, Experiment> = getAllExperimentsFromDatafile(datafile).reduce( + (experiments, experiment) => { + experiments[experiment.key] = experiment; + return experiments; + }, + {} as Record<string, Experiment> + ); + + featureFlags.forEach(featureFlag => { + const experimentIds = featureFlag.experimentIds; + experimentIds.forEach(experimentId => { + const experimentKey = projectConfigObject.experimentIdMap[experimentId].key; + const experiment = optimizelyConfigObject.experimentsMap[experimentKey]; + const variations = datafileExperimentsMap[experimentKey].variations; + const variationsMap = experiment.variationsMap; + variations.forEach(variation => { + featureFlag.variables.forEach(variable => { + const variableToAssert = variationsMap[variation.key].variablesMap[variable.key]; + // json is represented as sub type of string to support backwards compatibility in datafile. + // project config treats it as a first-class type. + const expectedVariableType = + variable.type === 'string' && variable.subType === 'json' ? 'json' : variable.type; + + expect({ + id: variable.id, + key: variable.key, + type: expectedVariableType, + }).toMatchObject({ + id: variableToAssert.id, + key: variableToAssert.key, + type: variableToAssert.type, + }); + + if (!variation.featureEnabled) { + expect(variable.defaultValue).toBe(variableToAssert.value); + } + }); + }); + }); + }); + }); + + it('should return correct config revision', () => { + expect(optimizelyConfigObject.revision).toBe(datafile.revision); + }); + + it('should return correct config sdkKey ', () => { + expect(optimizelyConfigObject.sdkKey).toBe(datafile.sdkKey); + }); + + it('should return correct config environmentKey ', () => { + expect(optimizelyConfigObject.environmentKey).toBe(datafile.environmentKey); + }); + + it('should return serialized audiences', () => { + const audiencesById = projectTypedAudienceConfigObject.audiencesById; + const audienceConditions = [ + ['or', '3468206642', '3988293898'], + ['or', '3468206642', '3988293898', '3468206646'], + ['not', '3468206642'], + ['or', '3468206642'], + ['and', '3468206642'], + ['3468206642'], + ['3468206642', '3988293898'], + ['and', ['or', '3468206642', '3988293898'], '3468206646'], + [ + 'and', + ['or', '3468206642', ['and', '3988293898', '3468206646']], + ['and', '3988293899', ['or', '3468206647', '3468206643']], + ], + ['and', 'and'], + ['not', ['and', '3468206642', '3988293898']], + [], + ['or', '3468206642', '999999999'], + ]; + + const expectedAudienceOutputs = [ + '"exactString" OR "substringString"', + '"exactString" OR "substringString" OR "exactNumber"', + 'NOT "exactString"', + '"exactString"', + '"exactString"', + '"exactString"', + '"exactString" OR "substringString"', + '("exactString" OR "substringString") AND "exactNumber"', + '("exactString" OR ("substringString" AND "exactNumber")) AND ("exists" AND ("gtNumber" OR "exactBoolean"))', + '', + 'NOT ("exactString" AND "substringString")', + '', + '"exactString" OR "999999999"', + ]; + + for (let testNo = 0; testNo < audienceConditions.length; testNo++) { + const serializedAudiences = OptimizelyConfig.getSerializedAudiences( + audienceConditions[testNo] as string[], + audiencesById + ); + + expect(serializedAudiences).toBe(expectedAudienceOutputs[testNo]); + } + }); + + it('should return correct rollouts', () => { + const rolloutFlag1 = optimizelySimilarRuleKeyConfigObject.featuresMap['flag_1'].deliveryRules[0]; + const rolloutFlag2 = optimizelySimilarRuleKeyConfigObject.featuresMap['flag_2'].deliveryRules[0]; + const rolloutFlag3 = optimizelySimilarRuleKeyConfigObject.featuresMap['flag_3'].deliveryRules[0]; + + expect(rolloutFlag1.id).toBe('9300000004977'); + expect(rolloutFlag1.key).toBe('targeted_delivery'); + expect(rolloutFlag2.id).toBe('9300000004979'); + expect(rolloutFlag2.key).toBe('targeted_delivery'); + expect(rolloutFlag3.id).toBe('9300000004981'); + expect(rolloutFlag3.key).toBe('targeted_delivery'); + }); + + it('should return default SDK and environment key', () => { + expect(optimizelySimilarRuleKeyConfigObject.sdkKey).toBe(''); + expect(optimizelySimilarRuleKeyConfigObject.environmentKey).toBe(''); + }); + + it('should return correct experiments with similar keys', function() { + expect(Object.keys(optimizelySimilarExperimentkeyConfigObject.experimentsMap).length).toBe(1); + + const experimentMapFlag1 = optimizelySimilarExperimentkeyConfigObject.featuresMap['flag1'].experimentsMap; + const experimentMapFlag2 = optimizelySimilarExperimentkeyConfigObject.featuresMap['flag2'].experimentsMap; + + expect(experimentMapFlag1['targeted_delivery'].id).toBe('9300000007569'); + expect(experimentMapFlag2['targeted_delivery'].id).toBe('9300000007573'); + }); +}); diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts new file mode 100644 index 000000000..2ab002bca --- /dev/null +++ b/lib/project_config/project_config.spec.ts @@ -0,0 +1,1132 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, afterEach, vi, assert, Mock } from 'vitest'; +import { sprintf } from '../utils/fns'; +import { keyBy } from '../utils/fns'; +import projectConfig, { ProjectConfig } from './project_config'; +import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; +import testDatafile from '../tests/test_data'; +import configValidator from '../utils/config_validator'; +import { + INVALID_EXPERIMENT_ID, + INVALID_EXPERIMENT_KEY, + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, + UNRECOGNIZED_ATTRIBUTE, + VARIABLE_KEY_NOT_IN_DATAFILE, + FEATURE_NOT_IN_DATAFILE, + UNABLE_TO_CAST_VALUE, +} from 'error_message'; +import { VariableType } from '../shared_types'; +import { OptimizelyError } from '../error/optimizly_error'; + +const createLogger = (...args: any) => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}); + +const buildLogMessageFromArgs = (args: any[]) => sprintf(args[1], ...args.splice(2)); +const cloneDeep = (obj: any) => JSON.parse(JSON.stringify(obj)); +const logger = createLogger(); + +describe('createProjectConfig', () => { + let configObj: ProjectConfig; + + it('should set properties correctly when createProjectConfig is called', () => { + const testData: Record<string, any> = testDatafile.getTestProjectConfig(); + configObj = projectConfig.createProjectConfig(testData as JSON); + + testData.audiences.forEach((audience: any) => { + audience.conditions = JSON.parse(audience.conditions); + }); + + expect(configObj.accountId).toBe(testData.accountId); + expect(configObj.projectId).toBe(testData.projectId); + expect(configObj.revision).toBe(testData.revision); + expect(configObj.events).toEqual(testData.events); + expect(configObj.audiences).toEqual(testData.audiences); + + testData.groups.forEach((group: any) => { + group.experiments.forEach((experiment: any) => { + experiment.groupId = group.id; + experiment.variationKeyMap = keyBy(experiment.variations, 'key'); + }); + }); + + expect(configObj.groups).toEqual(testData.groups); + + const expectedGroupIdMap = { + 666: testData.groups[0], + 667: testData.groups[1], + }; + + expect(configObj.groupIdMap).toEqual(expectedGroupIdMap); + + const expectedExperiments = testData.experiments.slice(); + + Object.entries(configObj.groupIdMap).forEach(([groupId, group]) => { + group.experiments.forEach((experiment: any) => { + experiment.groupId = groupId; + expectedExperiments.push(experiment); + }); + }) + expectedExperiments.forEach((experiment: any) => { + experiment.variationKeyMap = keyBy(experiment.variations, 'key'); + }) + + expect(configObj.experiments).toEqual(expectedExperiments); + + const expectedAttributeKeyMap = { + browser_type: testData.attributes[0], + boolean_key: testData.attributes[1], + integer_key: testData.attributes[2], + double_key: testData.attributes[3], + valid_positive_number: testData.attributes[4], + valid_negative_number: testData.attributes[5], + invalid_number: testData.attributes[6], + array: testData.attributes[7], + }; + + expect(configObj.attributeKeyMap).toEqual(expectedAttributeKeyMap); + + const expectedExperimentKeyMap = { + testExperiment: configObj.experiments[0], + testExperimentWithAudiences: configObj.experiments[1], + testExperimentNotRunning: configObj.experiments[2], + testExperimentLaunched: configObj.experiments[3], + groupExperiment1: configObj.experiments[4], + groupExperiment2: configObj.experiments[5], + overlappingGroupExperiment1: configObj.experiments[6], + }; + + expect(configObj.experimentKeyMap).toEqual(expectedExperimentKeyMap); + + const expectedEventKeyMap = { + testEvent: testData.events[0], + 'Total Revenue': testData.events[1], + testEventWithAudiences: testData.events[2], + testEventWithoutExperiments: testData.events[3], + testEventWithExperimentNotRunning: testData.events[4], + testEventWithMultipleExperiments: testData.events[5], + testEventLaunched: testData.events[6], + }; + + expect(configObj.eventKeyMap).toEqual(expectedEventKeyMap); + + const expectedExperimentIdMap = { + '111127': configObj.experiments[0], + '122227': configObj.experiments[1], + '133337': configObj.experiments[2], + '144447': configObj.experiments[3], + '442': configObj.experiments[4], + '443': configObj.experiments[5], + '444': configObj.experiments[6], + }; + + expect(configObj.experimentIdMap).toEqual(expectedExperimentIdMap); + }); + + it('should not mutate the datafile', () => { + const datafile = testDatafile.getTypedAudiencesConfig(); + const datafileClone = cloneDeep(datafile); + projectConfig.createProjectConfig(datafile as any); + + expect(datafile).toEqual(datafileClone); + }); +}); + +describe('createProjectConfig - feature management', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + }); + + it('should create a rolloutIdMap from rollouts in the datafile', () => { + expect(configObj.rolloutIdMap).toEqual(testDatafile.datafileWithFeaturesExpectedData.rolloutIdMap); + }); + + it('should create a variationVariableUsageMap from rollouts and experiments with features in the datafile', () => { + expect(configObj.variationVariableUsageMap).toEqual( + testDatafile.datafileWithFeaturesExpectedData.variationVariableUsageMap + ); + }); + + it('should create a featureKeyMap from features in the datafile', () => { + expect(configObj.featureKeyMap).toEqual(testDatafile.datafileWithFeaturesExpectedData.featureKeyMap); + }); + + it('should add variations from rollout experiements to the variationKeyMap', () => { + expect(configObj.variationIdMap['594032']).toEqual({ + variables: [ + { value: 'true', id: '4919852825313280' }, + { value: '395', id: '5482802778734592' }, + { value: '4.99', id: '6045752732155904' }, + { value: 'Hello audience', id: '6327227708866560' }, + { value: '{ "count": 2, "message": "Hello audience" }', id: '8765345281230956' }, + ], + featureEnabled: true, + key: '594032', + id: '594032', + }); + + expect(configObj.variationIdMap['594038']).toEqual({ + variables: [ + { value: 'false', id: '4919852825313280' }, + { value: '400', id: '5482802778734592' }, + { value: '14.99', id: '6045752732155904' }, + { value: 'Hello', id: '6327227708866560' }, + { value: '{ "count": 1, "message": "Hello" }', id: '8765345281230956' }, + ], + featureEnabled: false, + key: '594038', + id: '594038', + }); + + expect(configObj.variationIdMap['594061']).toEqual({ + variables: [ + { value: '27.34', id: '5060590313668608' }, + { value: 'Winter is NOT coming', id: '5342065290379264' }, + { value: '10003', id: '6186490220511232' }, + { value: 'false', id: '6467965197221888' }, + ], + featureEnabled: true, + key: '594061', + id: '594061', + }); + + expect(configObj.variationIdMap['594067']).toEqual({ + variables: [ + { value: '30.34', id: '5060590313668608' }, + { value: 'Winter is coming definitely', id: '5342065290379264' }, + { value: '500', id: '6186490220511232' }, + { value: 'true', id: '6467965197221888' }, + ], + featureEnabled: true, + key: '594067', + id: '594067', + }); + }); +}); + +describe('createProjectConfig - flag variations', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getTestDecideProjectConfig()); + }); + + it('should populate flagVariationsMap correctly', function() { + const allVariationsForFlag = configObj.flagVariationsMap; + const feature1Variations = allVariationsForFlag.feature_1; + const feature2Variations = allVariationsForFlag.feature_2; + const feature3Variations = allVariationsForFlag.feature_3; + const feature1VariationsKeys = feature1Variations.map(variation => { + return variation.key; + }, {}); + const feature2VariationsKeys = feature2Variations.map(variation => { + return variation.key; + }, {}); + const feature3VariationsKeys = feature3Variations.map(variation => { + return variation.key; + }, {}); + + expect(feature1VariationsKeys).toEqual(['a', 'b', '3324490633', '3324490562', '18257766532']); + expect(feature2VariationsKeys).toEqual(['variation_with_traffic', 'variation_no_traffic']); + expect(feature3VariationsKeys).toEqual([]); + }); +}); + +describe('getExperimentId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + let createdLogger: any; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + vi.spyOn(createdLogger, 'warn'); + }); + + it('should retrieve experiment ID for valid experiment key in getExperimentId', function() { + expect(projectConfig.getExperimentId(configObj, testData.experiments[0].key)).toBe(testData.experiments[0].id); + }); + + it('should throw error for invalid experiment key in getExperimentId', function() { + expect(() => { + projectConfig.getExperimentId(configObj, 'invalidExperimentId'); + }).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_KEY, + params: ['invalidExperimentId'], + }) + ); + }); +}); + +describe('getLayerId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve layer ID for valid experiment key in getLayerId', function() { + expect(projectConfig.getLayerId(configObj, '111127')).toBe('4'); + }); + + it('should throw error for invalid experiment key in getLayerId', function() { + // expect(() => projectConfig.getLayerId(configObj, 'invalidExperimentKey')).toThrowError( + // sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey') + // ); + expect(() => projectConfig.getLayerId(configObj, 'invalidExperimentKey')).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_ID, + params: ['invalidExperimentKey'], + }) + ); + }); +}); + +describe('getAttributeId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + let createdLogger: any; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + vi.spyOn(createdLogger, 'warn'); + }); + + it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { + expect(projectConfig.getAttributeId(configObj, 'browser_type')).toBe('111094'); + }); + + it('should retrieve attribute ID for reserved attribute key in getAttributeId', function() { + expect(projectConfig.getAttributeId(configObj, '$opt_user_agent')).toBe('$opt_user_agent'); + }); + + it('should return null for invalid attribute key in getAttributeId', function() { + expect(projectConfig.getAttributeId(configObj, 'invalidAttributeKey', createdLogger)).toBe(null); + expect(createdLogger.warn).toHaveBeenCalledWith(UNRECOGNIZED_ATTRIBUTE, 'invalidAttributeKey'); + }); + + it('should return null for invalid attribute key in getAttributeId', () => { + // Adding attribute in key map with reserved prefix + configObj.attributeKeyMap['$opt_some_reserved_attribute'] = { + id: '42', + }; + + expect(projectConfig.getAttributeId(configObj, '$opt_some_reserved_attribute', createdLogger)).toBe('42'); + expect(createdLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, + '$opt_some_reserved_attribute', + '$opt_' + ); + }); +}); + +describe('getEventId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve event ID for valid event key in getEventId', function() { + expect(projectConfig.getEventId(configObj, 'testEvent')).toBe('111095'); + }); + + it('should return null for invalid event key in getEventId', function() { + expect(projectConfig.getEventId(configObj, 'invalidEventKey')).toBe(null); + }); +}); + +describe('getExperimentStatus', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve experiment status for valid experiment key in getExperimentStatus', function() { + expect(projectConfig.getExperimentStatus(configObj, testData.experiments[0].key)).toBe( + testData.experiments[0].status + ); + }); + + it('should throw error for invalid experiment key in getExperimentStatus', function() { + expect(() => { + projectConfig.getExperimentStatus(configObj, 'invalidExeprimentKey'); + }).toThrowError(OptimizelyError); + }); +}); + +describe('isActive', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return true if experiment status is set to Running in isActive', function() { + expect(projectConfig.isActive(configObj, 'testExperiment')).toBe(true); + }); + + it('should return false if experiment status is not set to Running in isActive', function() { + expect(projectConfig.isActive(configObj, 'testExperimentNotRunning')).toBe(false); + }); +}); + +describe('isRunning', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(() => { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return true if experiment status is set to Running in isRunning', function() { + expect(projectConfig.isRunning(configObj, 'testExperiment')).toBe(true); + }); + + it('should return false if experiment status is not set to Running in isRunning', function() { + expect(projectConfig.isRunning(configObj, 'testExperimentLaunched')).toBe(false); + }); +}); + +describe('getVariationKeyFromId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + it('should retrieve variation key for valid experiment key and variation ID in getVariationKeyFromId', function() { + expect(projectConfig.getVariationKeyFromId(configObj, testData.experiments[0].variations[0].id)).toBe( + testData.experiments[0].variations[0].key + ); + }); +}); + +describe('getTrafficAllocation', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve traffic allocation given valid experiment key in getTrafficAllocation', function() { + expect(projectConfig.getTrafficAllocation(configObj, testData.experiments[0].id)).toEqual( + testData.experiments[0].trafficAllocation + ); + }); + + it('should throw error for invalid experient key in getTrafficAllocation', function() { + expect(() => { + projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); + }).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_ID, + params: ['invalidExperimentId'], + }) + ); + }); +}); + +describe('getVariationIdFromExperimentAndVariationKey', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return the variation id for the given experiment key and variation key', () => { + expect( + projectConfig.getVariationIdFromExperimentAndVariationKey( + configObj, + testData.experiments[0].key, + testData.experiments[0].variations[0].key + ) + ).toBe(testData.experiments[0].variations[0].id); + }); +}); + +describe('getSendFlagDecisionsValue', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return false when sendFlagDecisions is undefined', () => { + configObj.sendFlagDecisions = undefined; + + expect(projectConfig.getSendFlagDecisionsValue(configObj)).toBe(false); + }); + + it('should return false when sendFlagDecisions is set to false', () => { + configObj.sendFlagDecisions = false; + + expect(projectConfig.getSendFlagDecisionsValue(configObj)).toBe(false); + }); + + it('should return true when sendFlagDecisions is set to true', () => { + configObj.sendFlagDecisions = true; + + expect(projectConfig.getSendFlagDecisionsValue(configObj)).toBe(true); + }); +}); + +describe('getVariableForFeature', function() { + let featureManagementLogger: ReturnType<typeof createLogger>; + let configObj: ProjectConfig; + + beforeEach(() => { + featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + vi.spyOn(featureManagementLogger, 'warn'); + vi.spyOn(featureManagementLogger, 'error'); + vi.spyOn(featureManagementLogger, 'info'); + vi.spyOn(featureManagementLogger, 'debug'); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return a variable object for a valid variable and feature key', function() { + const featureKey = 'test_feature_for_experiment'; + const variableKey = 'num_buttons'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toEqual({ + type: 'integer', + key: 'num_buttons', + id: '4792309476491264', + defaultValue: '10', + }); + }); + + it('should return null for an invalid variable key and a valid feature key', function() { + const featureKey = 'test_feature_for_experiment'; + const variableKey = 'notARealVariable____'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledOnce(); + expect(featureManagementLogger.error).toHaveBeenCalledWith( + VARIABLE_KEY_NOT_IN_DATAFILE, + 'notARealVariable____', + 'test_feature_for_experiment' + ); + }); + + it('should return null for an invalid feature key', function() { + const featureKey = 'notARealFeature_____'; + const variableKey = 'num_buttons'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledOnce(); + expect(featureManagementLogger.error).toHaveBeenCalledWith(FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____'); + }); + + it('should return null for an invalid variable key and an invalid feature key', function() { + const featureKey = 'notARealFeature_____'; + const variableKey = 'notARealVariable____'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledOnce(); + expect(featureManagementLogger.error).toHaveBeenCalledWith(FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____'); + }); +}); + +describe('getVariableValueForVariation', () => { + let featureManagementLogger: ReturnType<typeof createLogger>; + let configObj: ProjectConfig; + + beforeEach(() => { + featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + vi.spyOn(featureManagementLogger, 'warn'); + vi.spyOn(featureManagementLogger, 'error'); + vi.spyOn(featureManagementLogger, 'info'); + vi.spyOn(featureManagementLogger, 'debug'); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return a value for a valid variation and variable', () => { + const variation = configObj.variationIdMap['594096']; + let variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + let result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + expect(result).toBe('2'); + + variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.is_button_animated; + result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe('true'); + + variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.button_txt; + result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe('Buy me NOW'); + + variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.button_width; + result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe('20.25'); + }); + + it('should return null for a null variation', () => { + const variation = null; + const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null for a null variable', () => { + const variation = configObj.variationIdMap['594096']; + const variable = null; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null for a null variation and null variable', () => { + const variation = null; + const variable = null; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null for a variation whose id is not in the datafile', () => { + const variation = { + key: 'some_variation', + id: '999999999999', + variables: [], + }; + const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null if the variation does not have a value for this variable', () => { + const variation = configObj.variationIdMap['595008']; // This variation has no variable values associated with it + const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); +}); + +describe('getTypeCastValue', () => { + let featureManagementLogger: ReturnType<typeof createLogger>; + let configObj: ProjectConfig; + + beforeEach(() => { + featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + vi.spyOn(featureManagementLogger, 'warn'); + vi.spyOn(featureManagementLogger, 'error'); + vi.spyOn(featureManagementLogger, 'info'); + vi.spyOn(featureManagementLogger, 'debug'); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should cast a boolean', () => { + let result = projectConfig.getTypeCastValue( + 'true', + FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType, + featureManagementLogger + ); + + expect(result).toBe(true); + + result = projectConfig.getTypeCastValue( + 'false', + FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType, + featureManagementLogger + ); + + expect(result).toBe(false); + }); + + it('should cast an integer', () => { + let result = projectConfig.getTypeCastValue( + '50', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(50); + + result = projectConfig.getTypeCastValue( + '-7', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(-7); + + result = projectConfig.getTypeCastValue( + '0', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(0); + }); + + it('should cast a double', () => { + let result = projectConfig.getTypeCastValue( + '89.99', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(89.99); + + result = projectConfig.getTypeCastValue( + '-257.21', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(-257.21); + + result = projectConfig.getTypeCastValue( + '0', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(0); + + result = projectConfig.getTypeCastValue( + '10', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(10); + }); + + it('should return a string unmodified', () => { + const result = projectConfig.getTypeCastValue( + 'message', + FEATURE_VARIABLE_TYPES.STRING as VariableType, + featureManagementLogger + ); + + expect(result).toBe('message'); + }); + + it('should return null and logs an error for an invalid boolean', () => { + const result = projectConfig.getTypeCastValue( + 'notabool', + FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType, + featureManagementLogger + ); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notabool', 'boolean'); + }); + + it('should return null and logs an error for an invalid integer', () => { + const result = projectConfig.getTypeCastValue( + 'notanint', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notanint', 'integer'); + }); + + it('should return null and logs an error for an invalid double', () => { + const result = projectConfig.getTypeCastValue( + 'notadouble', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notadouble', 'double'); + }); +}); + +describe('getAudiencesById', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); + }); + + it('should retrieve audiences by checking first in typedAudiences, and then second in audiences', () => { + expect(projectConfig.getAudiencesById(configObj)).toEqual(testDatafile.typedAudiencesById); + }); +}); + +describe('getExperimentAudienceConditions', () => { + let configObj: ProjectConfig; + let testData: Record<string, any>; + + beforeEach(() => { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + }); + + it('should retrieve audiences for valid experiment key', () => { + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + + expect(projectConfig.getExperimentAudienceConditions(configObj, testData.experiments[1].id)).toEqual(['11154']); + }); + + it('should throw error for invalid experiment key', () => { + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + + expect(() => { + projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); + }).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_ID, + params: ['invalidExperimentId'], + }) + ); + }); + + it('should return experiment audienceIds if experiment has no audienceConditions', () => { + configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); + const result = projectConfig.getExperimentAudienceConditions(configObj, '11564051718'); + + expect(result).toEqual([ + '3468206642', + '3988293898', + '3988293899', + '3468206646', + '3468206647', + '3468206644', + '3468206643', + ]); + }); + + it('should return experiment audienceConditions if experiment has audienceConditions', () => { + configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); + // audience_combinations_experiment has both audienceConditions and audienceIds + // audienceConditions should be preferred over audienceIds + const result = projectConfig.getExperimentAudienceConditions(configObj, '1323241598'); + + expect(result).toEqual([ + 'and', + ['or', '3468206642', '3988293898'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ]); + }); +}); + +describe('isFeatureExperiment', () => { + it('should return true for a feature test', () => { + const config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + const result = projectConfig.isFeatureExperiment(config, '594098'); // id of 'testing_my_feature' + + expect(result).toBe(true); + }); + + it('should return false for an A/B test', () => { + const config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig()); + const result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment' + + expect(result).toBe(false); + }); + + it('should return true for a feature test in a mutex group', () => { + const config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig()); + let result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1' + + expect(result).toBe(true); + + result = projectConfig.isFeatureExperiment(config, '17139931304'); // id of 'f_test2' + + expect(result).toBe(true); + }); +}); + +describe('getAudienceSegments', () => { + it('should return all qualified segments from an audience', () => { + const dummyQualifiedAudienceJson = { + id: '13389142234', + conditions: [ + 'and', + [ + 'or', + [ + 'or', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], + ], + name: 'odp-segment-1', + }; + + const dummyQualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyQualifiedAudienceJson); + + expect(dummyQualifiedAudienceJsonSegments).toEqual(['odp-segment-1']); + + const dummyUnqualifiedAudienceJson = { + id: '13389142234', + conditions: [ + 'and', + [ + 'or', + [ + 'or', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'invalid', + }, + ], + ], + ], + name: 'odp-segment-1', + }; + + const dummyUnqualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyUnqualifiedAudienceJson); + + expect(dummyUnqualifiedAudienceJsonSegments).toEqual([]); + }); +}); + +describe('integrations: with segments', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getOdpIntegratedConfigWithSegments()); + }); + + it('should convert integrations from the datafile into the project config', () => { + expect(configObj.integrations).toBeDefined(); + expect(configObj.integrations.length).toBe(4); + }); + + it('should populate odpIntegrationConfig', () => { + expect(configObj.odpIntegrationConfig.integrated).toBe(true); + + assert(configObj.odpIntegrationConfig.integrated); + + expect(configObj.odpIntegrationConfig.odpConfig.apiKey).toBe('W4WzcEs-ABgXorzY7h1LCQ'); + expect(configObj.odpIntegrationConfig.odpConfig.apiHost).toBe('https://api.zaius.com/'); + expect(configObj.odpIntegrationConfig.odpConfig.pixelUrl).toBe('https://jumbe.zaius.com/'); + expect(configObj.odpIntegrationConfig.odpConfig.segmentsToCheck).toEqual([ + 'odp-segment-1', + 'odp-segment-2', + 'odp-segment-3', + ]); + }); +}); + +describe('integrations: without segments', () => { + let config: ProjectConfig; + beforeEach(() => { + config = projectConfig.createProjectConfig(testDatafile.getOdpIntegratedConfigWithoutSegments()); + }); + + it('should convert integrations from the datafile into the project config', () => { + expect(config.integrations).toBeDefined(); + expect(config.integrations.length).toBe(3); + }); + + it('should populate odpIntegrationConfig', () => { + expect(config.odpIntegrationConfig.integrated).toBe(true); + + assert(config.odpIntegrationConfig.integrated); + + expect(config.odpIntegrationConfig.odpConfig.apiKey).toBe('W4WzcEs-ABgXorzY7h1LCQ'); + expect(config.odpIntegrationConfig.odpConfig.apiHost).toBe('https://api.zaius.com/'); + expect(config.odpIntegrationConfig.odpConfig.pixelUrl).toBe('https://jumbe.zaius.com/'); + expect(config.odpIntegrationConfig.odpConfig.segmentsToCheck).toEqual([]); + }); +}); + +describe('without valid integration key', () => { + it('should throw an error when parsing the project config due to integrations not containing a key', () => { + const odpIntegratedConfigWithoutKey = testDatafile.getOdpIntegratedConfigWithoutKey(); + + expect(() => projectConfig.createProjectConfig(odpIntegratedConfigWithoutKey)).toThrowError(OptimizelyError); + }); +}); + +describe('without integrations', () => { + let config: ProjectConfig; + + beforeEach(() => { + const odpIntegratedConfigWithSegments = testDatafile.getOdpIntegratedConfigWithSegments(); + const noIntegrationsConfigWithSegments = { ...odpIntegratedConfigWithSegments, integrations: [] }; + config = projectConfig.createProjectConfig(noIntegrationsConfigWithSegments); + }); + + it('should convert integrations from the datafile into the project config', () => { + expect(config.integrations.length).toBe(0); + }); + + it('should populate odpIntegrationConfig', () => { + expect(config.odpIntegrationConfig.integrated).toBe(false); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(config.odpIntegrationConfig.odpConfig).toBeUndefined(); + }); +}); + +describe('tryCreatingProjectConfig', () => { + let mockJsonSchemaValidator: Mock; + beforeEach(() => { + mockJsonSchemaValidator = vi.fn().mockReturnValue(true); + vi.spyOn(configValidator, 'validateDatafile').mockReturnValue(true); + vi.spyOn(logger, 'error'); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return a project config object created by createProjectConfig when all validation is applied and there are no errors', () => { + const configDatafile = { + foo: 'bar', + experiments: [{ key: 'a' }, { key: 'b' }], + }; + + vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(configDatafile); + + const configObj = { + foo: 'bar', + experimentKeyMap: { + a: { key: 'a', variationKeyMap: {} }, + b: { key: 'b', variationKeyMap: {} }, + }, + }; + + // stubJsonSchemaValidator.returns(true); + mockJsonSchemaValidator.mockReturnValueOnce(true); + + const result = projectConfig.tryCreatingProjectConfig({ + datafile: configDatafile, + jsonSchemaValidator: mockJsonSchemaValidator, + logger: logger, + }); + + expect(result).toMatchObject(configObj); + }); + + it('should throw an error when validateDatafile throws', function() { + vi.spyOn(configValidator, 'validateDatafile').mockImplementationOnce(() => { + throw new Error(); + }); + mockJsonSchemaValidator.mockReturnValueOnce(true); + + expect(() => + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: mockJsonSchemaValidator, + logger: logger, + }) + ).toThrowError(); + }); + + it('should throw an error when jsonSchemaValidator.validate throws', function() { + vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(true); + mockJsonSchemaValidator.mockImplementationOnce(() => { + throw new Error(); + }); + + expect(() => + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: mockJsonSchemaValidator, + logger: logger, + }) + ).toThrowError(); + }); + + it('should skip json validation when jsonSchemaValidator is not provided', function() { + const configDatafile = { + foo: 'bar', + experiments: [{ key: 'a' }, { key: 'b' }], + }; + + vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(configDatafile); + + const configObj = { + foo: 'bar', + experimentKeyMap: { + a: { key: 'a', variationKeyMap: {} }, + b: { key: 'b', variationKeyMap: {} }, + }, + }; + + const result = projectConfig.tryCreatingProjectConfig({ + datafile: configDatafile, + logger: logger, + }); + + expect(result).toMatchObject(configObj); + expect(logger.error).not.toHaveBeenCalled(); + }); +}); From 791ab90242feac1d0edb7279adbdcbb2b7e386f7 Mon Sep 17 00:00:00 2001 From: esrakartalOpt <102107327+esrakartalOpt@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:42:48 -0600 Subject: [PATCH 123/200] [FSSDK-11100] JS - rewrite audience_avaluator tests in TypeScript (#1001) * [FSSDK-11100] JS - rewrite audience_avaluator tests in TypeScript * Fix test case * Fix test cases * type fix * Fix failed test case * [FSSDK-11100] test fix * remove unnecessary restore * update --------- Co-authored-by: Raju Ahmed <raju.ahmed@optimizely.com> Co-authored-by: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> --- lib/core/audience_evaluator/index.spec.ts | 713 ++++++++++++++++++ .../index.spec.ts | 74 ++ 2 files changed, 787 insertions(+) create mode 100644 lib/core/audience_evaluator/index.spec.ts create mode 100644 lib/core/audience_evaluator/odp_segment_condition_evaluator/index.spec.ts diff --git a/lib/core/audience_evaluator/index.spec.ts b/lib/core/audience_evaluator/index.spec.ts new file mode 100644 index 000000000..e22654144 --- /dev/null +++ b/lib/core/audience_evaluator/index.spec.ts @@ -0,0 +1,713 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { beforeEach, afterEach, describe, it, vi, expect, afterAll } from 'vitest'; + +import AudienceEvaluator, { createAudienceEvaluator } from './index'; +import * as conditionTreeEvaluator from '../condition_tree_evaluator'; +import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from '../../message/log_message'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { Audience, OptimizelyDecideOption, OptimizelyDecision } from '../../shared_types'; +import { IOptimizelyUserContext } from '../../optimizely_user_context'; + +let mockLogger = getMockLogger(); + +const getMockUserContext = (attributes?: unknown, segments?: string[]): IOptimizelyUserContext => ({ + getAttributes: () => ({ ...(attributes || {}) }), + isQualifiedFor: segment => segments ? segments.indexOf(segment) > -1 : false, + qualifiedSegments: segments || [], + getUserId: () => 'mockUserId', + setAttribute: (key: string, value: any) => {}, + + decide: (key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision => ({ + variationKey: 'mockVariationKey', + enabled: true, + variables: { mockVariable: 'mockValue' }, + ruleKey: 'mockRuleKey', + reasons: ['mockReason'], + flagKey: 'flagKey', + userContext: getMockUserContext() + }), +}) as IOptimizelyUserContext; + +const chromeUserAudience = { + id: '0', + name: 'chromeUserAudience', + conditions: [ + 'and', + { + name: 'browser_type', + value: 'chrome', + type: 'custom_attribute', + }, + ], +}; +const iphoneUserAudience = { + id: '1', + name: 'iphoneUserAudience', + conditions: [ + 'and', + { + name: 'device_model', + value: 'iphone', + type: 'custom_attribute', + }, + ], +}; +const specialConditionTypeAudience = { + id: '3', + name: 'specialConditionTypeAudience', + conditions: [ + 'and', + { + match: 'interest_level', + value: 'special', + type: 'special_condition_type', + }, + ], +}; +const conditionsPassingWithNoAttrs = [ + 'not', + { + match: 'exists', + name: 'input_value', + type: 'custom_attribute', + }, +]; +const conditionsPassingWithNoAttrsAudience = { + id: '2', + name: 'conditionsPassingWithNoAttrsAudience', + conditions: conditionsPassingWithNoAttrs, +}; + +const audiencesById: { +[id: string]: Audience; +} = { + "0": chromeUserAudience, + "1": iphoneUserAudience, + "2": conditionsPassingWithNoAttrsAudience, + "3": specialConditionTypeAudience, +}; + + +describe('lib/core/audience_evaluator', () => { + let audienceEvaluator: AudienceEvaluator; + + beforeEach(() => { + mockLogger = getMockLogger(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('APIs', () => { + describe('with default condition evaluator', () => { + beforeEach(() => { + audienceEvaluator = createAudienceEvaluator({}); + }); + describe('evaluate', () => { + it('should return true if there are no audiences', () => { + expect(audienceEvaluator.evaluate([], audiencesById, getMockUserContext({}))).toBe(true); + }); + + it('should return false if there are audiences but no attributes', () => { + expect(audienceEvaluator.evaluate(['0'], audiencesById, getMockUserContext({}))).toBe(false); + }); + + it('should return true if any of the audience conditions are met', () => { + const iphoneUsers = { + device_model: 'iphone', + }; + + const chromeUsers = { + browser_type: 'chrome', + }; + + const iphoneChromeUsers = { + browser_type: 'chrome', + device_model: 'iphone', + }; + + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(iphoneUsers))).toBe(true); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(chromeUsers))).toBe(true); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(iphoneChromeUsers))).toBe( + true + ); + }); + + it('should return false if none of the audience conditions are met', () => { + const nexusUsers = { + device_model: 'nexus5', + }; + + const safariUsers = { + browser_type: 'safari', + }; + + const nexusSafariUsers = { + browser_type: 'safari', + device_model: 'nexus5', + }; + + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(nexusUsers))).toBe(false); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(safariUsers))).toBe(false); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(nexusSafariUsers))).toBe( + false + ); + }); + + it('should return true if no attributes are passed and the audience conditions evaluate to true in the absence of attributes', () => { + expect(audienceEvaluator.evaluate(['2'], audiencesById, getMockUserContext({}))).toBe(true); + }); + + describe('complex audience conditions', () => { + it('should return true if any of the audiences in an "OR" condition pass', () => { + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ browser_type: 'chrome' }) + ); + expect(result).toBe(true); + }); + + it('should return true if all of the audiences in an "AND" condition pass', () => { + const result = audienceEvaluator.evaluate( + ['and', '0', '1'], + audiencesById, + getMockUserContext({ + browser_type: 'chrome', + device_model: 'iphone', + }) + ); + expect(result).toBe(true); + }); + + it('should return true if the audience in a "NOT" condition does not pass', () => { + const result = audienceEvaluator.evaluate( + ['not', '1'], + audiencesById, + getMockUserContext({ device_model: 'android' }) + ); + expect(result).toBe(true); + }); + }); + + describe('integration with dependencies', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + afterAll(() => { + vi.resetAllMocks(); + }); + + it('returns true if conditionTreeEvaluator.evaluate returns true', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(true); + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ browser_type: 'chrome' }) + ); + expect(result).toBe(true); + }); + + it('returns false if conditionTreeEvaluator.evaluate returns false', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(false); + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ browser_type: 'safari' }) + ); + expect(result).toBe(false); + }); + + it('returns false if conditionTreeEvaluator.evaluate returns null', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(null); + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ state: 'California' }) + ); + expect(result).toBe(false); + }); + + it('calls customAttributeConditionEvaluator.evaluate in the leaf evaluator for audience conditions', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementation((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + const mockCustomAttributeConditionEvaluator = vi.fn().mockReturnValue(false); + + vi.spyOn(customAttributeConditionEvaluator, 'getEvaluator').mockReturnValue({ + evaluate: mockCustomAttributeConditionEvaluator, + }); + + const audienceEvaluator = createAudienceEvaluator({}); + + const userAttributes = { device_model: 'android' }; + const user = getMockUserContext(userAttributes); + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith( + iphoneUserAudience.conditions[1], + user, + ); + + expect(result).toBe(false); + }); + }); + + describe('Audience evaluation logging', () => { + let mockCustomAttributeConditionEvaluator: ReturnType<typeof vi.fn>; + + beforeEach(() => { + mockCustomAttributeConditionEvaluator = vi.fn(); + vi.spyOn(conditionTreeEvaluator, 'evaluate'); + vi.spyOn(customAttributeConditionEvaluator, 'getEvaluator').mockReturnValue({ + evaluate: mockCustomAttributeConditionEvaluator, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('logs correctly when conditionTreeEvaluator.evaluate returns null', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + mockCustomAttributeConditionEvaluator.mockReturnValue(null); + const userAttributes = { device_model: 5.5 }; + const user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith(iphoneUserAudience.conditions[1], user); + expect(result).toBe(false); + expect(mockLogger.debug).toHaveBeenCalledTimes(2); + + expect(mockLogger.debug).toHaveBeenCalledWith( + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ); + + expect(mockLogger.debug).toHaveBeenCalledWith(AUDIENCE_EVALUATION_RESULT, '1', 'UNKNOWN'); + }); + + it('logs correctly when conditionTreeEvaluator.evaluate returns true', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + mockCustomAttributeConditionEvaluator.mockReturnValue(true); + + const userAttributes = { device_model: 'iphone' }; + const user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith(iphoneUserAudience.conditions[1], user); + expect(result).toBe(true); + expect(mockLogger.debug).toHaveBeenCalledTimes(2) + expect(mockLogger.debug).toHaveBeenCalledWith( + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ); + + expect(mockLogger.debug).toHaveBeenCalledWith(AUDIENCE_EVALUATION_RESULT, '1', 'TRUE'); + }); + + it('logs correctly when conditionTreeEvaluator.evaluate returns false', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + mockCustomAttributeConditionEvaluator.mockReturnValue(false); + + const userAttributes = { device_model: 'android' }; + const user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith(iphoneUserAudience.conditions[1], user); + expect(result).toBe(false); + expect(mockLogger.debug).toHaveBeenCalledTimes(2) + expect(mockLogger.debug).toHaveBeenCalledWith( + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ); + + expect(mockLogger.debug).toHaveBeenCalledWith(AUDIENCE_EVALUATION_RESULT, '1', 'FALSE'); + }); + }); + }); + }); + + describe('with additional custom condition evaluator', () => { + describe('when passing a valid additional evaluator', () => { + beforeEach(() => { + const mockEnvironment = { + special: true, + }; + audienceEvaluator = createAudienceEvaluator({ + special_condition_type: { + evaluate: (condition: any, user: any) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = mockEnvironment[condition.value] && user.getAttributes()[condition.match] > 0; + return result; + }, + }, + }); + }); + + it('should evaluate an audience properly using the custom condition evaluator', () => { + expect(audienceEvaluator.evaluate(['3'], audiencesById, getMockUserContext({ interest_level: 0 }))).toBe( + false + ); + expect(audienceEvaluator.evaluate(['3'], audiencesById, getMockUserContext({ interest_level: 1 }))).toBe( + true + ); + }); + }); + + describe('when passing an invalid additional evaluator', () => { + beforeEach(() => { + audienceEvaluator = createAudienceEvaluator({ + custom_attribute: { + evaluate: () => { + return false; + }, + }, + }); + }); + + it('should not be able to overwrite built in `custom_attribute` evaluator', () => { + expect( + audienceEvaluator.evaluate( + ['0'], + audiencesById, + getMockUserContext({ + browser_type: 'chrome', + }) + ) + ).toBe(true); + }); + }); + }); + + describe('with odp segment evaluator', () => { + describe('Single ODP Audience', () => { + const singleAudience = { + id: '0', + name: 'singleAudience', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + const audiencesById = { + 0: singleAudience, + }; + const audience = new AudienceEvaluator({}); + + it('should evaluate to true if segment is found', () => { + expect(audience.evaluate(['or', '0'], audiencesById, getMockUserContext({}, ['odp-segment-1']))).toBe(true); + }); + + it('should evaluate to false if segment is not found', () => { + expect(audience.evaluate(['or', '0'], audiencesById, getMockUserContext({}, ['odp-segment-2']))).toBe(false); + }); + + it('should evaluate to false if not segments are provided', () => { + expect(audience.evaluate(['or', '0'], audiencesById, getMockUserContext({}))).toBe(false); + }); + }); + + describe('Multiple ODP conditions in one Audience', () => { + const singleAudience = { + id: '0', + name: 'singleAudience', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + [ + 'or', + { + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-4', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], + }; + const audiencesById = { + 0: singleAudience, + }; + const audience = new AudienceEvaluator({}); + + it('should evaluate correctly based on the given segments', () => { + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-4']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(false); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-2', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(false); + }); + }); + + describe('Multiple ODP conditions in multiple Audience', () => { + const audience1And2 = { + id: '0', + name: 'audience1And2', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + + const audience3And4 = { + id: '1', + name: 'audience3And4', + conditions: [ + 'and', + { + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-4', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + + const audience5And6 = { + id: '2', + name: 'audience5And6', + conditions: [ + 'or', + { + value: 'odp-segment-5', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-6', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + const audiencesById = { + 0: audience1And2, + 1: audience3And4, + 2: audience5And6, + }; + const audience = new AudienceEvaluator({}); + + it('should evaluate correctly based on the given segments', () => { + expect( + audience.evaluate( + ['or', '0', '1', '2'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2']) + ) + ).toBe(false); + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({}, [ + 'odp-segment-1', + 'odp-segment-2', + 'odp-segment-3', + 'odp-segment-4', + 'odp-segment-6', + ]) + ) + ).toBe(true); + expect( + audience.evaluate( + ['and', '0', '1', ['not', '2']], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(true); + }); + }); + }); + + describe('with multiple types of evaluators', () => { + const audience1And2 = { + id: '0', + name: 'audience1And2', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + const audience3Or4 = { + id: '', + name: 'audience3And4', + conditions: [ + 'or', + { + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-4', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + + const audiencesById = { + 0: audience1And2, + 1: audience3Or4, + 2: chromeUserAudience, + }; + + const audience = new AudienceEvaluator({}); + + it('should evaluate correctly based on the given segments', () => { + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({ browser_type: 'not_chrome' }, ['odp-segment-1', 'odp-segment-2', 'odp-segment-4']) + ) + ).toBe(false); + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({ browser_type: 'chrome' }, ['odp-segment-1', 'odp-segment-2', 'odp-segment-4']) + ) + ).toBe(true); + }); + }); + }); +}); diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.spec.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.spec.ts new file mode 100644 index 000000000..f42d07cb4 --- /dev/null +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.spec.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { afterEach, describe, it, vi, expect } from 'vitest'; +import * as odpSegmentEvalutor from '.'; +import { UNKNOWN_MATCH_TYPE } from '../../../message/error_message'; +import { IOptimizelyUserContext } from '../../../optimizely_user_context'; +import { OptimizelyDecideOption, OptimizelyDecision } from '../../../shared_types'; +import { getMockLogger } from '../../../tests/mock/mock_logger'; + +const odpSegment1Condition = { + "value": "odp-segment-1", + "type": "third_party_dimension", + "name": "odp.audiences", + "match": "qualified" +}; + +const getMockUserContext = (attributes?: unknown, segments?: string[]): IOptimizelyUserContext => ({ + getAttributes: () => ({ ...(attributes || {}) }), + isQualifiedFor: segment => segments ? segments.indexOf(segment) > -1 : false, + qualifiedSegments: segments || [], + getUserId: () => 'mockUserId', + setAttribute: (key: string, value: any) => {}, + + decide: (key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision => ({ + variationKey: 'mockVariationKey', + enabled: true, + variables: { mockVariable: 'mockValue' }, + ruleKey: 'mockRuleKey', + reasons: ['mockReason'], + flagKey: 'flagKey', + userContext: getMockUserContext() + }), +}) as IOptimizelyUserContext; + + +describe('lib/core/audience_evaluator/odp_segment_condition_evaluator', function() { + const mockLogger = getMockLogger(); + const { evaluate } = odpSegmentEvalutor.getEvaluator(mockLogger); + + afterEach(function() { + vi.restoreAllMocks(); + }); + + it('should return true when segment qualifies and known match type is provided', () => { + expect(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-1']))).toBe(true); + }); + + it('should return false when segment does not qualify and known match type is provided', () => { + expect(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-2']))).toBe(false); + }) + + it('should return null when segment qualifies but unknown match type is provided', () => { + const invalidOdpMatchCondition = { + ... odpSegment1Condition, + "match": 'unknown', + }; + expect(evaluate(invalidOdpMatchCondition, getMockUserContext({}, ['odp-segment-1']))).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNKNOWN_MATCH_TYPE, JSON.stringify(invalidOdpMatchCondition)); + }); +}); From 1f82bdba2eb63b719f061daaf695814f00d5097f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 17 Feb 2025 23:56:07 +0600 Subject: [PATCH 124/200] [FSSDK-11114] refactor sdk entrypoints (#1003) --- lib/client_factory.ts | 75 +++++++++++++ lib/entrypoint.test-d.ts | 38 +++++++ .../event_processor_factory.browser.spec.ts | 65 ++++++------ .../event_processor_factory.browser.ts | 15 ++- .../event_processor_factory.node.spec.ts | 73 +++++++------ .../event_processor_factory.node.ts | 19 ++-- ...ent_processor_factory.react_native.spec.ts | 77 +++++++------- .../event_processor_factory.react_native.ts | 16 ++- .../event_processor_factory.ts | 25 ++++- lib/index.browser.tests.js | 100 ++++++++---------- lib/index.browser.ts | 81 +++----------- lib/index.node.tests.js | 8 +- lib/index.node.ts | 47 ++------ lib/index.react_native.spec.ts | 20 ++-- lib/index.react_native.ts | 55 ++-------- lib/logging/logger_factory.ts | 9 +- lib/odp/odp_manager_factory.browser.spec.ts | 43 ++++---- lib/odp/odp_manager_factory.browser.ts | 6 +- lib/odp/odp_manager_factory.node.spec.ts | 51 ++++----- lib/odp/odp_manager_factory.node.ts | 6 +- .../odp_manager_factory.react_native.spec.ts | 51 ++++----- lib/odp/odp_manager_factory.react_native.ts | 6 +- lib/odp/odp_manager_factory.ts | 16 +++ lib/optimizely/index.spec.ts | 3 +- .../config_manager_factory.browser.spec.ts | 15 +-- .../config_manager_factory.browser.ts | 6 +- .../config_manager_factory.node.spec.ts | 15 +-- .../config_manager_factory.node.ts | 6 +- ...onfig_manager_factory.react_native.spec.ts | 17 +-- .../config_manager_factory.react_native.ts | 8 +- lib/project_config/config_manager_factory.ts | 28 ++++- lib/shared_types.ts | 13 ++- lib/utils/enums/index.ts | 2 - lib/vuid/vuid_manager_factory.browser.spec.ts | 15 +-- lib/vuid/vuid_manager_factory.browser.ts | 8 +- lib/vuid/vuid_manager_factory.node.ts | 5 +- .../vuid_manager_factory.react_native.spec.ts | 15 +-- lib/vuid/vuid_manager_factory.react_native.ts | 8 +- lib/vuid/vuid_manager_factory.ts | 17 +++ package.json | 2 +- vitest.config.mts | 1 + 41 files changed, 595 insertions(+), 491 deletions(-) create mode 100644 lib/client_factory.ts create mode 100644 lib/entrypoint.test-d.ts diff --git a/lib/client_factory.ts b/lib/client_factory.ts new file mode 100644 index 000000000..898df5575 --- /dev/null +++ b/lib/client_factory.ts @@ -0,0 +1,75 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerFacade } from "./logging/logger"; +import { Client, Config } from "./shared_types"; +import { Maybe } from "./utils/type"; +import configValidator from './utils/config_validator'; +import { extractLogger } from "./logging/logger_factory"; +import { extractErrorNotifier } from "./error/error_notifier_factory"; +import { extractConfigManager } from "./project_config/config_manager_factory"; +import { extractEventProcessor } from "./event_processor/event_processor_factory"; +import { extractOdpManager } from "./odp/odp_manager_factory"; +import { extractVuidManager } from "./vuid/vuid_manager_factory"; + +import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums"; +import Optimizely from "./optimizely"; + +export const getOptimizelyInstance = (config: Config): Client | null => { + let logger: Maybe<LoggerFacade>; + + try { + logger = config.logger ? extractLogger(config.logger) : undefined; + + configValidator.validate(config); + + const { + clientEngine, + clientVersion, + jsonSchemaValidator, + userProfileService, + defaultDecideOptions, + disposable, + } = config; + + const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; + + const projectConfigManager = extractConfigManager(config.projectConfigManager); + const eventProcessor = config.eventProcessor ? extractEventProcessor(config.eventProcessor) : undefined; + const odpManager = config.odpManager ? extractOdpManager(config.odpManager) : undefined; + const vuidManager = config.vuidManager ? extractVuidManager(config.vuidManager) : undefined; + + const optimizelyOptions = { + clientEngine: clientEngine || JAVASCRIPT_CLIENT_ENGINE, + clientVersion: clientVersion || CLIENT_VERSION, + jsonSchemaValidator, + userProfileService, + defaultDecideOptions, + disposable, + logger, + errorNotifier, + projectConfigManager, + eventProcessor, + odpManager, + vuidManager, + }; + + return new Optimizely(optimizelyOptions); + } catch (e) { + logger?.error(e); + return null; + } +} diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts new file mode 100644 index 000000000..f408688b2 --- /dev/null +++ b/lib/entrypoint.test-d.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expectTypeOf } from 'vitest'; + +import * as browserEntrypoint from './index.browser'; +import * as nodeEntrypoint from './index.node'; +import * as reactNativeEntrypoint from './index.react_native'; + +import { Config, Client } from './shared_types'; + +export type Entrypoint = { + createInstance: (config: Config) => Client | null; +} + + +// these type tests will be fixed in a future PR + +// expectTypeOf(browserEntrypoint).toEqualTypeOf<Entrypoint>(); +// expectTypeOf(nodeEntrypoint).toEqualTypeOf<Entrypoint>(); +// expectTypeOf(reactNativeEntrypoint).toEqualTypeOf<Entrypoint>(); + +// expectTypeOf(browserEntrypoint).toEqualTypeOf(nodeEntrypoint); +// expectTypeOf(browserEntrypoint).toEqualTypeOf(reactNativeEntrypoint); +// expectTypeOf(nodeEntrypoint).toEqualTypeOf(reactNativeEntrypoint); diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts index b0b636efb..dcc7ce497 100644 --- a/lib/event_processor/event_processor_factory.browser.spec.ts +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -30,8 +30,11 @@ vi.mock('./event_processor_factory', async (importOriginal) => { const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); + const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); const original: any = await importOriginal(); - return { ...original, getBatchEventProcessor }; + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor }; }); vi.mock('../utils/cache/local_storage_cache.browser', () => { @@ -47,11 +50,11 @@ import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browse import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { SyncPrefixCache } from '../utils/cache/cache'; import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.browser'; -import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import browserDefaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; -import { getBatchEventProcessor } from './event_processor_factory'; +import { getOpaqueBatchEventProcessor } from './event_processor_factory'; describe('createForwardingEventProcessor', () => { const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); @@ -65,14 +68,14 @@ describe('createForwardingEventProcessor', () => { dispatchEvent: vi.fn(), }; - const processor = createForwardingEventProcessor(eventDispatcher); + const processor = extractEventProcessor(createForwardingEventProcessor(eventDispatcher)); expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); }); it('uses the browser default event dispatcher if none is provided', () => { - const processor = createForwardingEventProcessor(); + const processor = extractEventProcessor(createForwardingEventProcessor()); expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, browserDefaultEventDispatcher); @@ -80,20 +83,20 @@ describe('createForwardingEventProcessor', () => { }); describe('createBatchEventProcessor', () => { - const mockGetBatchEventProcessor = vi.mocked(getBatchEventProcessor); + const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); const MockLocalStorageCache = vi.mocked(LocalStorageCache); const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); beforeEach(() => { - mockGetBatchEventProcessor.mockClear(); + mockGetOpaqueBatchEventProcessor.mockClear(); MockLocalStorageCache.mockClear(); MockSyncPrefixCache.mockClear(); }); it('uses LocalStorageCache and SyncPrefixCache to create eventStore', () => { const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - const eventStore = mockGetBatchEventProcessor.mock.calls[0][0].eventStore; + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; expect(Object.is(eventStore, MockSyncPrefixCache.mock.results[0].value)).toBe(true); const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; @@ -111,14 +114,14 @@ describe('createBatchEventProcessor', () => { }; const processor = createBatchEventProcessor({ eventDispatcher }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); }); it('uses the default browser event dispatcher if none is provided', () => { const processor = createBatchEventProcessor({ }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); }); it('uses the provided closingEventDispatcher', () => { @@ -127,8 +130,8 @@ describe('createBatchEventProcessor', () => { }; const processor = createBatchEventProcessor({ closingEventDispatcher }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); }); it('does not use any closingEventDispatcher if eventDispatcher is provided but closingEventDispatcher is not', () => { @@ -137,45 +140,45 @@ describe('createBatchEventProcessor', () => { }; const processor = createBatchEventProcessor({ eventDispatcher }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(undefined); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(undefined); }); it('uses the default sendBeacon event dispatcher if neither eventDispatcher nor closingEventDispatcher is provided', () => { const processor = createBatchEventProcessor({ }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(sendBeaconEventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(sendBeaconEventDispatcher); }); it('uses the provided flushInterval', () => { const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); - expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); const processor2 = createBatchEventProcessor({ }); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); }); it('uses the provided batchSize', () => { const processor1 = createBatchEventProcessor({ batchSize: 20 }); - expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); const processor2 = createBatchEventProcessor({ }); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); }); it('uses maxRetries value of 5', () => { const processor = createBatchEventProcessor({ }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); }); it('uses the default failedEventRetryInterval', () => { const processor = createBatchEventProcessor({ }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); }); }); diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index b6651ed70..0ce034dc7 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -18,7 +18,12 @@ import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor'; import { EventWithId } from './batch_event_processor'; -import { getBatchEventProcessor, BatchEventProcessorOptions } from './event_processor_factory'; +import { + getOpaqueBatchEventProcessor, + BatchEventProcessorOptions, + OpaqueEventProcessor, + wrapEventProcessor, +} from './event_processor_factory'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; @@ -27,15 +32,15 @@ import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_process export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, -): EventProcessor => { - return getForwardingEventProcessor(eventDispatcher); +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); }; const identity = <T>(v: T): T => v; export const createBatchEventProcessor = ( options: BatchEventProcessorOptions -): EventProcessor => { +): OpaqueEventProcessor => { const localStorageCache = new LocalStorageCache<EventWithId>(); const eventStore = new SyncPrefixCache<EventWithId, EventWithId>( localStorageCache, EVENT_STORE_PREFIX, @@ -43,7 +48,7 @@ export const createBatchEventProcessor = ( identity, ); - return getBatchEventProcessor({ + return getOpaqueBatchEventProcessor({ eventDispatcher: options.eventDispatcher || defaultEventDispatcher, closingEventDispatcher: options.closingEventDispatcher || (options.eventDispatcher ? undefined : sendBeaconEventDispatcher), diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts index 31001400f..487230748 100644 --- a/lib/event_processor/event_processor_factory.node.spec.ts +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -28,8 +28,11 @@ vi.mock('./event_processor_factory', async (importOriginal) => { const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); + const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); const original: any = await importOriginal(); - return { ...original, getBatchEventProcessor }; + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor }; }); vi.mock('../utils/cache/async_storage_cache.react_native', () => { @@ -43,8 +46,8 @@ vi.mock('../utils/cache/cache', () => { import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor_factory.node'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import nodeDefaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; -import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; -import { getBatchEventProcessor } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { getOpaqueBatchEventProcessor } from './event_processor_factory'; import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; @@ -60,14 +63,14 @@ describe('createForwardingEventProcessor', () => { dispatchEvent: vi.fn(), }; - const processor = createForwardingEventProcessor(eventDispatcher); + const processor = extractEventProcessor(createForwardingEventProcessor(eventDispatcher)); expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); }); it('uses the node default event dispatcher if none is provided', () => { - const processor = createForwardingEventProcessor(); + const processor = extractEventProcessor(createForwardingEventProcessor()); expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, nodeDefaultEventDispatcher); @@ -75,13 +78,13 @@ describe('createForwardingEventProcessor', () => { }); describe('createBatchEventProcessor', () => { - const mockGetBatchEventProcessor = vi.mocked(getBatchEventProcessor); + const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); const MockAsyncPrefixCache = vi.mocked(AsyncPrefixCache); beforeEach(() => { - mockGetBatchEventProcessor.mockClear(); + mockGetOpaqueBatchEventProcessor.mockClear(); MockAsyncStorageCache.mockClear(); MockSyncPrefixCache.mockClear(); MockAsyncPrefixCache.mockClear(); @@ -90,8 +93,8 @@ describe('createBatchEventProcessor', () => { it('uses no default event store if no eventStore is provided', () => { const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - const eventStore = mockGetBatchEventProcessor.mock.calls[0][0].eventStore; + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; expect(eventStore).toBe(undefined); }); @@ -101,9 +104,9 @@ describe('createBatchEventProcessor', () => { } as SyncCache<string>; const processor = createBatchEventProcessor({ eventStore }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; expect(cache).toBe(eventStore); @@ -120,9 +123,9 @@ describe('createBatchEventProcessor', () => { } as AsyncCache<string>; const processor = createBatchEventProcessor({ eventStore }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; expect(cache).toBe(eventStore); @@ -140,14 +143,14 @@ describe('createBatchEventProcessor', () => { }; const processor = createBatchEventProcessor({ eventDispatcher }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); }); it('uses the default node event dispatcher if none is provided', () => { const processor = createBatchEventProcessor({ }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(nodeDefaultEventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(nodeDefaultEventDispatcher); }); it('uses the provided closingEventDispatcher', () => { @@ -156,49 +159,49 @@ describe('createBatchEventProcessor', () => { }; const processor = createBatchEventProcessor({ closingEventDispatcher }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); const processor2 = createBatchEventProcessor({ }); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); }); it('uses the provided flushInterval', () => { const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); - expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); const processor2 = createBatchEventProcessor({ }); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); }); it('uses the provided batchSize', () => { const processor1 = createBatchEventProcessor({ batchSize: 20 }); - expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); const processor2 = createBatchEventProcessor({ }); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); }); it('uses maxRetries value of 10', () => { const processor = createBatchEventProcessor({ }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(10); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(10); }); it('uses no failed event retry if an eventStore is not provided', () => { const processor = createBatchEventProcessor({ }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(undefined); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(undefined); }); it('uses the default failedEventRetryInterval if an eventStore is provided', () => { const processor = createBatchEventProcessor({ eventStore: {} as any }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); }); }); diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index 6c57272bc..4671ce3a3 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -15,23 +15,28 @@ */ import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; -import { EventProcessor } from './event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; -import { BatchEventProcessorOptions, FAILED_EVENT_RETRY_INTERVAL, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; +import { + BatchEventProcessorOptions, + FAILED_EVENT_RETRY_INTERVAL, + getOpaqueBatchEventProcessor, + getPrefixEventStore, + OpaqueEventProcessor, + wrapEventProcessor, +} from './event_processor_factory'; export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, -): EventProcessor => { - return getForwardingEventProcessor(eventDispatcher); +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); }; - export const createBatchEventProcessor = ( options: BatchEventProcessorOptions -): EventProcessor => { +): OpaqueEventProcessor => { const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : undefined; - return getBatchEventProcessor({ + return getOpaqueBatchEventProcessor({ eventDispatcher: options.eventDispatcher || defaultEventDispatcher, closingEventDispatcher: options.closingEventDispatcher, flushInterval: options.flushInterval, diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 30e300dc9..131654a79 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -29,8 +29,11 @@ vi.mock('./event_processor_factory', async importOriginal => { const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); + const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); const original: any = await importOriginal(); - return { ...original, getBatchEventProcessor }; + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor }; }); vi.mock('../utils/cache/async_storage_cache.react_native', () => { @@ -74,8 +77,8 @@ async function mockRequireNetInfo() { import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; -import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL, getPrefixEventStore } from './event_processor_factory'; -import { getBatchEventProcessor } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL, getPrefixEventStore } from './event_processor_factory'; +import { getOpaqueBatchEventProcessor } from './event_processor_factory'; import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; @@ -95,14 +98,14 @@ describe('createForwardingEventProcessor', () => { dispatchEvent: vi.fn(), }; - const processor = createForwardingEventProcessor(eventDispatcher); + const processor = extractEventProcessor(createForwardingEventProcessor(eventDispatcher)); expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); }); it('uses the browser default event dispatcher if none is provided', () => { - const processor = createForwardingEventProcessor(); + const processor = extractEventProcessor(createForwardingEventProcessor()); expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, defaultEventDispatcher); @@ -110,14 +113,14 @@ describe('createForwardingEventProcessor', () => { }); describe('createBatchEventProcessor', () => { - const mockGetBatchEventProcessor = vi.mocked(getBatchEventProcessor); + const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); const MockAsyncPrefixCache = vi.mocked(AsyncPrefixCache); beforeEach(() => { isNetInfoAvailable = false; - mockGetBatchEventProcessor.mockClear(); + mockGetOpaqueBatchEventProcessor.mockClear(); MockAsyncStorageCache.mockClear(); MockSyncPrefixCache.mockClear(); MockAsyncPrefixCache.mockClear(); @@ -126,22 +129,22 @@ describe('createBatchEventProcessor', () => { it('returns an instance of ReacNativeNetInfoEventProcessor if netinfo can be required', async () => { isNetInfoAvailable = true; const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][1]).toBe(ReactNativeNetInfoEventProcessor); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][1]).toBe(ReactNativeNetInfoEventProcessor); }); it('returns an instance of BatchEventProcessor if netinfo cannot be required', async () => { isNetInfoAvailable = false; const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][1]).toBe(BatchEventProcessor); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][1]).toBe(BatchEventProcessor); }); it('uses AsyncStorageCache and AsyncPrefixCache to create eventStore if no eventStore is provided', () => { const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - const eventStore = mockGetBatchEventProcessor.mock.calls[0][0].eventStore; + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; expect(Object.is(eventStore, MockAsyncPrefixCache.mock.results[0].value)).toBe(true); const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; @@ -195,9 +198,9 @@ describe('createBatchEventProcessor', () => { } as SyncCache<string>; const processor = createBatchEventProcessor({ eventStore }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; expect(cache).toBe(eventStore); @@ -214,9 +217,9 @@ describe('createBatchEventProcessor', () => { } as AsyncCache<string>; const processor = createBatchEventProcessor({ eventStore }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; expect(cache).toBe(eventStore); @@ -233,14 +236,14 @@ describe('createBatchEventProcessor', () => { }; const processor = createBatchEventProcessor({ eventDispatcher }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); }); it('uses the default browser event dispatcher if none is provided', () => { const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); }); it('uses the provided closingEventDispatcher', () => { @@ -249,43 +252,43 @@ describe('createBatchEventProcessor', () => { }; const processor = createBatchEventProcessor({ closingEventDispatcher }); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); const processor2 = createBatchEventProcessor({}); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); }); it('uses the provided flushInterval', () => { const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); - expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); const processor2 = createBatchEventProcessor({}); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); }); it('uses the provided batchSize', () => { const processor1 = createBatchEventProcessor({ batchSize: 20 }); - expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); const processor2 = createBatchEventProcessor({}); - expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); }); it('uses maxRetries value of 5', () => { const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); }); it('uses the default failedEventRetryInterval', () => { const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); }); }); diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index ce300ac79..fefb3f816 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -17,7 +17,13 @@ import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; -import { BatchEventProcessorOptions, getBatchEventProcessor, getPrefixEventStore } from './event_processor_factory'; +import { + BatchEventProcessorOptions, + getOpaqueBatchEventProcessor, + getPrefixEventStore, + OpaqueEventProcessor, + wrapEventProcessor, +} from './event_processor_factory'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { AsyncPrefixCache } from '../utils/cache/cache'; import { BatchEventProcessor, EventWithId } from './batch_event_processor'; @@ -27,8 +33,8 @@ import { isAvailable as isNetInfoAvailable } from '../utils/import.react_native/ export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, -): EventProcessor => { - return getForwardingEventProcessor(eventDispatcher); +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); }; const identity = <T>(v: T): T => v; @@ -48,10 +54,10 @@ const getDefaultEventStore = () => { export const createBatchEventProcessor = ( options: BatchEventProcessorOptions -): EventProcessor => { +): OpaqueEventProcessor => { const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : getDefaultEventStore(); - return getBatchEventProcessor({ + return getOpaqueBatchEventProcessor({ eventDispatcher: options.eventDispatcher || defaultEventDispatcher, closingEventDispatcher: options.closingEventDispatcher, flushInterval: options.flushInterval, diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index 70f1b6310..fe7f838f7 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -46,6 +46,12 @@ export const getPrefixEventStore = (cache: Cache<string>): Cache<EventWithId> => } }; +const eventProcessorSymbol: unique symbol = Symbol(); + +export type OpaqueEventProcessor = { + [eventProcessorSymbol]: unknown; +}; + export type BatchEventProcessorOptions = { eventDispatcher?: EventDispatcher; closingEventDispatcher?: EventDispatcher; @@ -118,4 +124,21 @@ export const getBatchEventProcessor = ( eventStore, startupLogs, }); -}; +} + +export const wrapEventProcessor = (eventProcessor: EventProcessor): OpaqueEventProcessor => { + return { + [eventProcessorSymbol]: eventProcessor, + }; +} + +export const getOpaqueBatchEventProcessor = ( + options: BatchEventProcessorFactoryOptions, + EventProcessorConstructor: typeof BatchEventProcessor = BatchEventProcessor +): OpaqueEventProcessor => { + return wrapEventProcessor(getBatchEventProcessor(options, EventProcessorConstructor)); +} + +export const extractEventProcessor = (eventProcessor: OpaqueEventProcessor): EventProcessor => { + return eventProcessor[eventProcessorSymbol] as EventProcessor; +} diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 5fb84a30f..3ea249904 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -18,10 +18,12 @@ import sinon from 'sinon'; import Optimizely from './optimizely'; import testData from './tests/test_data'; import packageJSON from '../package.json'; -import optimizelyFactory from './index.browser'; +import * as optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; +import { wrapConfigManager } from './project_config/config_manager_factory'; +import { wrapLogger } from './logging/logger_factory'; class MockLocalStorage { store = {}; @@ -69,13 +71,17 @@ var getLogger = () => ({ describe('javascript-sdk (Browser)', function() { var clock; + + before(() => { + window.addEventListener = () => {}; + // sinon.spy(window, 'addEventListener') + }); + beforeEach(function() { - sinon.stub(optimizelyFactory.eventDispatcher, 'dispatchEvent'); clock = sinon.useFakeTimers(new Date()); }); afterEach(function() { - optimizelyFactory.eventDispatcher.dispatchEvent.restore(); clock.restore(); }); @@ -103,7 +109,6 @@ describe('javascript-sdk (Browser)', function() { }); afterEach(function() { - optimizelyFactory.__internalResetRetryState(); mockLogger.error.restore(); configValidator.validate.restore(); delete global.XMLHttpRequest; @@ -114,7 +119,7 @@ describe('javascript-sdk (Browser)', function() { // logic, not the dispatcher. Refactor accordingly. // it('should invoke resendPendingEvents at most once', function() { // var optlyInstance = optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager(), + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), // errorHandler: fakeErrorHandler, // logger: silentLogger, // }); @@ -122,7 +127,7 @@ describe('javascript-sdk (Browser)', function() { // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); // optlyInstance = optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager(), + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), // errorHandler: fakeErrorHandler, // logger: silentLogger, // }); @@ -135,18 +140,17 @@ describe('javascript-sdk (Browser)', function() { configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - logger: mockLogger, + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + logger: wrapLogger(mockLogger), }); }); }); it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: mockLogger, + logger: wrapLogger(mockLogger), }); assert.instanceOf(optlyInstance, Optimizely); @@ -155,10 +159,9 @@ describe('javascript-sdk (Browser)', function() { it('should set the JavaScript client engine and version', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: mockLogger, + logger: wrapLogger(mockLogger), }); assert.equal('javascript-sdk', optlyInstance.clientEngine); @@ -168,22 +171,19 @@ describe('javascript-sdk (Browser)', function() { it('should allow passing of "react-sdk" as the clientEngine', function() { var optlyInstance = optimizelyFactory.createInstance({ clientEngine: 'react-sdk', - projectConfigManager: getMockProjectConfigManager(), + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: mockLogger, + logger: wrapLogger(mockLogger), }); assert.equal('react-sdk', optlyInstance.clientEngine); }); it('should activate with provided event dispatcher', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var activate = optlyInstance.activate('testExperiment', 'testUser'); assert.strictEqual(activate, 'control'); @@ -191,12 +191,10 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set and get a forced variation', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -208,12 +206,10 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set and unset a forced variation', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -231,12 +227,10 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set multiple experiments for one user', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -258,12 +252,10 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set multiple experiments for one user, and unset one', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -288,12 +280,10 @@ describe('javascript-sdk (Browser)', function() { it('should be able to set multiple experiments for one user, and reset one', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -322,12 +312,10 @@ describe('javascript-sdk (Browser)', function() { it('should override bucketing when setForcedVariation is called', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); @@ -345,12 +333,10 @@ describe('javascript-sdk (Browser)', function() { it('should override bucketing when setForcedVariation is called for a not running experiment', function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), - }), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: mockLogger, + })), + logger: wrapLogger(mockLogger), }); var didSetVariation = optlyInstance.setForcedVariation( diff --git a/lib/index.browser.ts b/lib/index.browser.ts index bdb10fe42..4190a4b9f 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -33,14 +33,9 @@ import { extractLogger, createLogger } from './logging/logger_factory'; import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; import { LoggerFacade } from './logging/logger'; import { Maybe } from './utils/type'; +import { getOptimizelyInstance } from './client_factory'; -const DEFAULT_EVENT_BATCH_SIZE = 10; -const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s -const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; - -let hasRetriedEvents = false; - /** * Creates an instance of the Optimizely class * @param {Config} config @@ -48,59 +43,27 @@ let hasRetriedEvents = false; * null on error */ const createInstance = function(config: Config): Client | null { - let logger: Maybe<LoggerFacade>; - - try { - configValidator.validate(config); - - const { clientEngine, clientVersion } = config; - logger = config.logger ? extractLogger(config.logger) : undefined; - const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; - - const optimizelyOptions = { - ...config, - clientEngine: clientEngine || enums.JAVASCRIPT_CLIENT_ENGINE, - clientVersion: clientVersion || enums.CLIENT_VERSION, - logger, - errorNotifier, - }; - - const optimizely = new Optimizely(optimizelyOptions); - - try { - if (typeof window.addEventListener === 'function') { - const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; - window.addEventListener( - unloadEvent, - () => { - optimizely.close(); - }, - false - ); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e) { - logger?.error(UNABLE_TO_ATTACH_UNLOAD, e.message); - } - - return optimizely; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e) { - logger?.error(e); - return null; + const client = getOptimizelyInstance(config); + + if (client) { + const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; + window.addEventListener( + unloadEvent, + () => { + client.close(); + }, + ); } -}; -const __internalResetRetryState = function(): void { - hasRetriedEvents = false; + return client; }; + export { defaultEventDispatcher as eventDispatcher, - sendBeaconEventDispatcher, + // sendBeaconEventDispatcher, enums, createInstance, - __internalResetRetryState, OptimizelyDecideOption, UserAgentParser as IUserAgentParser, getUserAgentParser, @@ -115,20 +78,4 @@ export { export * from './common_exports'; -export default { - ...commonExports, - eventDispatcher: defaultEventDispatcher, - sendBeaconEventDispatcher, - enums, - createInstance, - __internalResetRetryState, - OptimizelyDecideOption, - getUserAgentParser, - createPollingProjectConfigManager, - createForwardingEventProcessor, - createBatchEventProcessor, - createOdpManager, - createVuidManager, -}; - export * from './export_types'; diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index f35903418..0146fffab 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -18,9 +18,11 @@ import sinon from 'sinon'; import Optimizely from './optimizely'; import testData from './tests/test_data'; -import optimizelyFactory from './index.node'; +import * as optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { wrapConfigManager } from './project_config/config_manager_factory'; +import { wrapLogger } from './logging/logger_factory'; var createLogger = () => ({ debug: () => {}, @@ -72,8 +74,8 @@ describe('optimizelyFactory', function() { configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), - logger: fakeLogger, + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + logger: wrapLogger(fakeLogger), }); }); // sinon.assert.calledOnce(fakeLogger.error); diff --git a/lib/index.node.ts b/lib/index.node.ts index c0d7b41db..d3959c75c 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -// import { getLogger, setErrorHandler, getErrorHandler, LogLevel, setLogHandler, setLogLevel } from './modules/logging'; -import Optimizely from './optimizely'; import * as enums from './utils/enums'; -import configValidator from './utils/config_validator'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config } from './shared_types'; @@ -31,10 +28,7 @@ import { extractErrorNotifier, createErrorNotifier } from './error/error_notifie import { Maybe } from './utils/type'; import { LoggerFacade } from './logging/logger'; import { ErrorNotifier } from './error/error_notifier'; - -const DEFAULT_EVENT_BATCH_SIZE = 10; -const DEFAULT_EVENT_FLUSH_INTERVAL = 30000; // Unit is ms, default is 30s -const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; +import { getOptimizelyInstance } from './client_factory'; /** * Creates an instance of the Optimizely class @@ -43,29 +37,12 @@ const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; * null on error */ const createInstance = function(config: Config): Client | null { - let logger: Maybe<LoggerFacade>; - - try { - configValidator.validate(config); - - const { clientEngine, clientVersion } = config; - logger = config.logger ? extractLogger(config.logger) : undefined; - const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; - - const optimizelyOptions = { - ...config, - clientEngine: clientEngine || enums.NODE_CLIENT_ENGINE, - clientVersion: clientVersion || enums.CLIENT_VERSION, - logger, - errorNotifier, - }; - - return new Optimizely(optimizelyOptions); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e) { - logger?.error(e); - return null; + const nodeConfig = { + ...config, + clientEnging: config.clientEngine || enums.NODE_CLIENT_ENGINE, } + + return getOptimizelyInstance(nodeConfig); }; /** @@ -87,17 +64,5 @@ export { export * from './common_exports'; -export default { - ...commonExports, - eventDispatcher: defaultEventDispatcher, - enums, - createInstance, - OptimizelyDecideOption, - createPollingProjectConfigManager, - createForwardingEventProcessor, - createBatchEventProcessor, - createOdpManager, - createVuidManager, -}; export * from './export_types'; diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index 42ba24821..d091e889a 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -18,11 +18,13 @@ import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import Optimizely from './optimizely'; import testData from './tests/test_data'; import packageJSON from '../package.json'; -import optimizelyFactory from './index.react_native'; +import * as optimizelyFactory from './index.react_native'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; import { getMockLogger } from './tests/mock/mock_logger'; +import { wrapConfigManager } from './project_config/config_manager_factory'; +import { wrapLogger } from './logging/logger_factory'; vi.mock('@react-native-community/netinfo'); vi.mock('react-native-get-random-values') @@ -39,10 +41,10 @@ describe('javascript-sdk/react-native', () => { }); describe('APIs', () => { - it('should expose logger, errorHandler, eventDispatcher and enums', () => { - expect(optimizelyFactory.eventDispatcher).toBeDefined(); - expect(optimizelyFactory.enums).toBeDefined(); - }); + // it('should expose logger, errorHandler, eventDispatcher and enums', () => { + // expect(optimizelyFactory.eventDispatcher).toBeDefined(); + // expect(optimizelyFactory.enums).toBeDefined(); + // }); describe('createInstance', () => { const fakeErrorHandler = { handleError: function() {} }; @@ -70,17 +72,17 @@ describe('javascript-sdk/react-native', () => { }); expect(function() { const optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - logger: mockLogger, + logger: wrapLogger(mockLogger), }); }).not.toThrow(); }); it('should create an instance of optimizely', () => { const optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), // errorHandler: fakeErrorHandler, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -95,7 +97,7 @@ describe('javascript-sdk/react-native', () => { it('should set the React Native JS client engine and javascript SDK version', () => { const optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: getMockProjectConfigManager(), + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), // errorHandler: fakeErrorHandler, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 243d1fea3..f135d40ba 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import * as enums from './utils/enums'; import Optimizely from './optimizely'; import configValidator from './utils/config_validator'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; @@ -32,10 +30,8 @@ import { Maybe } from './utils/type'; import { LoggerFacade } from './logging/logger'; import { extractLogger, createLogger } from './logging/logger_factory'; import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; - -const DEFAULT_EVENT_BATCH_SIZE = 10; -const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s -const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; +import { getOptimizelyInstance } from './client_factory'; +import { REACT_NATIVE_JS_CLIENT_ENGINE } from './utils/enums'; /** * Creates an instance of the Optimizely class @@ -44,35 +40,12 @@ const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; * null on error */ const createInstance = function(config: Config): Client | null { - let logger: Maybe<LoggerFacade>; - - try { - configValidator.validate(config); - - const { clientEngine, clientVersion } = config; - - logger = config.logger ? extractLogger(config.logger) : undefined; - const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; - - const optimizelyOptions = { - ...config, - clientEngine: clientEngine || enums.REACT_NATIVE_JS_CLIENT_ENGINE, - clientVersion: clientVersion || enums.CLIENT_VERSION, - logger, - errorNotifier, - }; - - // If client engine is react, convert it to react native. - if (optimizelyOptions.clientEngine === enums.REACT_CLIENT_ENGINE) { - optimizelyOptions.clientEngine = enums.REACT_NATIVE_CLIENT_ENGINE; - } - - return new Optimizely(optimizelyOptions); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e) { - logger?.error(e); - return null; + const rnConfig = { + ...config, + clientEngine: config.clientEngine || REACT_NATIVE_JS_CLIENT_ENGINE, } + + return getOptimizelyInstance(rnConfig); }; /** @@ -80,7 +53,6 @@ const createInstance = function(config: Config): Client | null { */ export { defaultEventDispatcher as eventDispatcher, - enums, createInstance, OptimizelyDecideOption, createPollingProjectConfigManager, @@ -94,17 +66,4 @@ export { export * from './common_exports'; -export default { - ...commonExports, - eventDispatcher: defaultEventDispatcher, - enums, - createInstance, - OptimizelyDecideOption, - createPollingProjectConfigManager, - createForwardingEventProcessor, - createBatchEventProcessor, - createOdpManager, - createVuidManager, -}; - export * from './export_types'; diff --git a/lib/logging/logger_factory.ts b/lib/logging/logger_factory.ts index 09d3440fc..0b4335d01 100644 --- a/lib/logging/logger_factory.ts +++ b/lib/logging/logger_factory.ts @@ -97,6 +97,13 @@ export const createLogger = (config: LoggerConfig): OpaqueLogger => { }; }; +export const wrapLogger = (logger: OptimizelyLogger): OpaqueLogger => { + return { + [loggerSymbol]: logger, + }; +}; + export const extractLogger = (logger: OpaqueLogger): OptimizelyLogger => { return logger[loggerSymbol] as OptimizelyLogger; -} +}; + diff --git a/lib/odp/odp_manager_factory.browser.spec.ts b/lib/odp/odp_manager_factory.browser.spec.ts index 534046f94..16b4183c8 100644 --- a/lib/odp/odp_manager_factory.browser.spec.ts +++ b/lib/odp/odp_manager_factory.browser.spec.ts @@ -19,29 +19,32 @@ vi.mock('../utils/http_request_handler/request_handler.browser', () => { }); vi.mock('./odp_manager_factory', () => { - return { getOdpManager: vi.fn().mockImplementation(() => ({})) }; + return { + getOdpManager: vi.fn().mockImplementation(() => ({})), + getOpaqueOdpManager: vi.fn().mockImplementation(() => ({})) + }; }); import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { getOpaqueOdpManager, OdpManagerOptions } from './odp_manager_factory'; import { BROWSER_DEFAULT_API_TIMEOUT, createOdpManager } from './odp_manager_factory.browser'; import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; describe('createOdpManager', () => { const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); - const mockGetOdpManager = vi.mocked(getOdpManager); + const mockGetOpaqueOdpManager = vi.mocked(getOpaqueOdpManager); beforeEach(() => { MockBrowserRequestHandler.mockClear(); - mockGetOdpManager.mockClear(); + mockGetOpaqueOdpManager.mockClear(); }); it('should use BrowserRequestHandler with the provided timeout as the segment request handler', () => { const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; expect(requestHandlerOptions?.timeout).toBe(3456); @@ -49,8 +52,8 @@ describe('createOdpManager', () => { it('should use BrowserRequestHandler with the browser default timeout as the segment request handler', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; expect(requestHandlerOptions?.timeout).toBe(BROWSER_DEFAULT_API_TIMEOUT); @@ -58,8 +61,8 @@ describe('createOdpManager', () => { it('should use BrowserRequestHandler with the provided timeout as the event request handler', () => { const odpManager = createOdpManager({ eventApiTimeout: 2345 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; expect(requestHandlerOptions?.timeout).toBe(2345); @@ -67,8 +70,8 @@ describe('createOdpManager', () => { it('should use BrowserRequestHandler with the browser default timeout as the event request handler', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; expect(requestHandlerOptions?.timeout).toBe(BROWSER_DEFAULT_API_TIMEOUT); @@ -76,22 +79,22 @@ describe('createOdpManager', () => { it('should use batchSize 1 if batchSize is not provided', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventBatchSize).toBe(1); }); it('should use batchSize 1 event if some other batchSize value is provided', () => { const odpManager = createOdpManager({ eventBatchSize: 99 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventBatchSize).toBe(1); }); it('uses the pixel api request generator', () => { const odpManager = createOdpManager({ }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestGenerator } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestGenerator).toBe(pixelApiRequestGenerator); }); @@ -106,7 +109,7 @@ describe('createOdpManager', () => { userAgentParser: {} as any, }; const odpManager = createOdpManager(options); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - expect(mockGetOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + expect(mockGetOpaqueOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); }); }); diff --git a/lib/odp/odp_manager_factory.browser.ts b/lib/odp/odp_manager_factory.browser.ts index f70dfe976..2090dcb87 100644 --- a/lib/odp/odp_manager_factory.browser.ts +++ b/lib/odp/odp_manager_factory.browser.ts @@ -17,11 +17,11 @@ import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { OdpManager } from './odp_manager'; -import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; export const BROWSER_DEFAULT_API_TIMEOUT = 10_000; -export const createOdpManager = (options: OdpManagerOptions): OdpManager => { +export const createOdpManager = (options: OdpManagerOptions): OpaqueOdpManager => { const segmentRequestHandler = new BrowserRequestHandler({ timeout: options.segmentsApiTimeout || BROWSER_DEFAULT_API_TIMEOUT, }); @@ -30,7 +30,7 @@ export const createOdpManager = (options: OdpManagerOptions): OdpManager => { timeout: options.eventApiTimeout || BROWSER_DEFAULT_API_TIMEOUT, }); - return getOdpManager({ + return getOpaqueOdpManager({ ...options, eventBatchSize: 1, segmentRequestHandler, diff --git a/lib/odp/odp_manager_factory.node.spec.ts b/lib/odp/odp_manager_factory.node.spec.ts index 491fd7520..f89d6ce94 100644 --- a/lib/odp/odp_manager_factory.node.spec.ts +++ b/lib/odp/odp_manager_factory.node.spec.ts @@ -19,29 +19,32 @@ vi.mock('../utils/http_request_handler/request_handler.node', () => { }); vi.mock('./odp_manager_factory', () => { - return { getOdpManager: vi.fn().mockImplementation(() => ({})) }; + return { + getOdpManager: vi.fn().mockImplementation(() => ({})), + getOpaqueOdpManager: vi.fn().mockImplementation(() => ({})) + }; }); import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { getOpaqueOdpManager, OdpManagerOptions } from './odp_manager_factory'; import { NODE_DEFAULT_API_TIMEOUT, NODE_DEFAULT_BATCH_SIZE, NODE_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.node'; import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; describe('createOdpManager', () => { const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); - const mockGetOdpManager = vi.mocked(getOdpManager); + const mockGetOpaqueOdpManager = vi.mocked(getOpaqueOdpManager); beforeEach(() => { MockNodeRequestHandler.mockClear(); - mockGetOdpManager.mockClear(); + mockGetOpaqueOdpManager.mockClear(); }); it('should use NodeRequestHandler with the provided timeout as the segment request handler', () => { const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(segmentRequestHandler).toBe(MockNodeRequestHandler.mock.instances[0]); const requestHandlerOptions = MockNodeRequestHandler.mock.calls[0][0]; expect(requestHandlerOptions?.timeout).toBe(3456); @@ -49,8 +52,8 @@ describe('createOdpManager', () => { it('should use NodeRequestHandler with the node default timeout as the segment request handler', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(segmentRequestHandler).toBe(MockNodeRequestHandler.mock.instances[0]); const requestHandlerOptions = MockNodeRequestHandler.mock.calls[0][0]; expect(requestHandlerOptions?.timeout).toBe(NODE_DEFAULT_API_TIMEOUT); @@ -58,8 +61,8 @@ describe('createOdpManager', () => { it('should use NodeRequestHandler with the provided timeout as the event request handler', () => { const odpManager = createOdpManager({ eventApiTimeout: 2345 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestHandler).toBe(MockNodeRequestHandler.mock.instances[1]); const requestHandlerOptions = MockNodeRequestHandler.mock.calls[1][0]; expect(requestHandlerOptions?.timeout).toBe(2345); @@ -67,8 +70,8 @@ describe('createOdpManager', () => { it('should use NodeRequestHandler with the node default timeout as the event request handler', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestHandler).toBe(MockNodeRequestHandler.mock.instances[1]); const requestHandlerOptions = MockNodeRequestHandler.mock.calls[1][0]; expect(requestHandlerOptions?.timeout).toBe(NODE_DEFAULT_API_TIMEOUT); @@ -76,36 +79,36 @@ describe('createOdpManager', () => { it('uses the event api request generator', () => { const odpManager = createOdpManager({ }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestGenerator } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestGenerator).toBe(eventApiRequestGenerator); }); it('should use the provided eventBatchSize', () => { const odpManager = createOdpManager({ eventBatchSize: 99 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventBatchSize).toBe(99); }); it('should use the node default eventBatchSize if none provided', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventBatchSize).toBe(NODE_DEFAULT_BATCH_SIZE); }); it('should use the provided eventFlushInterval', () => { const odpManager = createOdpManager({ eventFlushInterval: 9999 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventFlushInterval).toBe(9999); }); it('should use the node default eventFlushInterval if none provided', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventFlushInterval).toBe(NODE_DEFAULT_FLUSH_INTERVAL); }); @@ -119,7 +122,7 @@ describe('createOdpManager', () => { userAgentParser: {} as any, }; const odpManager = createOdpManager(options); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - expect(mockGetOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + expect(mockGetOpaqueOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); }); }); diff --git a/lib/odp/odp_manager_factory.node.ts b/lib/odp/odp_manager_factory.node.ts index f2438dbd9..35d223462 100644 --- a/lib/odp/odp_manager_factory.node.ts +++ b/lib/odp/odp_manager_factory.node.ts @@ -17,13 +17,13 @@ import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { OdpManager } from './odp_manager'; -import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; export const NODE_DEFAULT_API_TIMEOUT = 10_000; export const NODE_DEFAULT_BATCH_SIZE = 10; export const NODE_DEFAULT_FLUSH_INTERVAL = 1000; -export const createOdpManager = (options: OdpManagerOptions): OdpManager => { +export const createOdpManager = (options: OdpManagerOptions): OpaqueOdpManager => { const segmentRequestHandler = new NodeRequestHandler({ timeout: options.segmentsApiTimeout || NODE_DEFAULT_API_TIMEOUT, }); @@ -32,7 +32,7 @@ export const createOdpManager = (options: OdpManagerOptions): OdpManager => { timeout: options.eventApiTimeout || NODE_DEFAULT_API_TIMEOUT, }); - return getOdpManager({ + return getOpaqueOdpManager({ ...options, segmentRequestHandler, eventRequestHandler, diff --git a/lib/odp/odp_manager_factory.react_native.spec.ts b/lib/odp/odp_manager_factory.react_native.spec.ts index 640e9cf4e..fd703d362 100644 --- a/lib/odp/odp_manager_factory.react_native.spec.ts +++ b/lib/odp/odp_manager_factory.react_native.spec.ts @@ -19,29 +19,32 @@ vi.mock('../utils/http_request_handler/request_handler.browser', () => { }); vi.mock('./odp_manager_factory', () => { - return { getOdpManager: vi.fn().mockImplementation(() => ({})) }; + return { + getOdpManager: vi.fn().mockImplementation(() => ({})), + getOpaqueOdpManager: vi.fn().mockImplementation(() => ({})), + }; }); import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { getOpaqueOdpManager, OdpManagerOptions } from './odp_manager_factory'; import { RN_DEFAULT_API_TIMEOUT, RN_DEFAULT_BATCH_SIZE, RN_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.react_native'; import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser' import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; describe('createOdpManager', () => { const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); - const mockGetOdpManager = vi.mocked(getOdpManager); + const mockGetOpaqueOdpManager = vi.mocked(getOpaqueOdpManager); beforeEach(() => { MockBrowserRequestHandler.mockClear(); - mockGetOdpManager.mockClear(); + mockGetOpaqueOdpManager.mockClear(); }); it('should use BrowserRequestHandler with the provided timeout as the segment request handler', () => { const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; expect(requestHandlerOptions?.timeout).toBe(3456); @@ -49,8 +52,8 @@ describe('createOdpManager', () => { it('should use BrowserRequestHandler with the node default timeout as the segment request handler', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { segmentRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; expect(requestHandlerOptions?.timeout).toBe(RN_DEFAULT_API_TIMEOUT); @@ -58,8 +61,8 @@ describe('createOdpManager', () => { it('should use BrowserRequestHandler with the provided timeout as the event request handler', () => { const odpManager = createOdpManager({ eventApiTimeout: 2345 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; expect(requestHandlerOptions?.timeout).toBe(2345); @@ -67,8 +70,8 @@ describe('createOdpManager', () => { it('should use BrowserRequestHandler with the node default timeout as the event request handler', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestHandler } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; expect(requestHandlerOptions?.timeout).toBe(RN_DEFAULT_API_TIMEOUT); @@ -76,36 +79,36 @@ describe('createOdpManager', () => { it('uses the event api request generator', () => { const odpManager = createOdpManager({ }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventRequestGenerator } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventRequestGenerator).toBe(eventApiRequestGenerator); }); it('should use the provided eventBatchSize', () => { const odpManager = createOdpManager({ eventBatchSize: 99 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventBatchSize).toBe(99); }); it('should use the react_native default eventBatchSize if none provided', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventBatchSize } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventBatchSize).toBe(RN_DEFAULT_BATCH_SIZE); }); it('should use the provided eventFlushInterval', () => { const odpManager = createOdpManager({ eventFlushInterval: 9999 }); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventFlushInterval).toBe(9999); }); it('should use the react_native default eventFlushInterval if none provided', () => { const odpManager = createOdpManager({}); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - const { eventFlushInterval } = mockGetOdpManager.mock.calls[0][0]; + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; expect(eventFlushInterval).toBe(RN_DEFAULT_FLUSH_INTERVAL); }); @@ -119,7 +122,7 @@ describe('createOdpManager', () => { userAgentParser: {} as any, }; const odpManager = createOdpManager(options); - expect(odpManager).toBe(mockGetOdpManager.mock.results[0].value); - expect(mockGetOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + expect(mockGetOpaqueOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); }); }); diff --git a/lib/odp/odp_manager_factory.react_native.ts b/lib/odp/odp_manager_factory.react_native.ts index 1ba0bcc5c..854ba32bc 100644 --- a/lib/odp/odp_manager_factory.react_native.ts +++ b/lib/odp/odp_manager_factory.react_native.ts @@ -17,13 +17,13 @@ import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { OdpManager } from './odp_manager'; -import { getOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; export const RN_DEFAULT_API_TIMEOUT = 10_000; export const RN_DEFAULT_BATCH_SIZE = 10; export const RN_DEFAULT_FLUSH_INTERVAL = 1000; -export const createOdpManager = (options: OdpManagerOptions): OdpManager => { +export const createOdpManager = (options: OdpManagerOptions): OpaqueOdpManager => { const segmentRequestHandler = new BrowserRequestHandler({ timeout: options.segmentsApiTimeout || RN_DEFAULT_API_TIMEOUT, }); @@ -32,7 +32,7 @@ export const createOdpManager = (options: OdpManagerOptions): OdpManager => { timeout: options.eventApiTimeout || RN_DEFAULT_API_TIMEOUT, }); - return getOdpManager({ + return getOpaqueOdpManager({ ...options, segmentRequestHandler, eventRequestHandler, diff --git a/lib/odp/odp_manager_factory.ts b/lib/odp/odp_manager_factory.ts index 31d908df1..12d229d4b 100644 --- a/lib/odp/odp_manager_factory.ts +++ b/lib/odp/odp_manager_factory.ts @@ -34,6 +34,12 @@ export const DEFAULT_EVENT_MAX_RETRIES = 5; export const DEFAULT_EVENT_MIN_BACKOFF = 1000; export const DEFAULT_EVENT_MAX_BACKOFF = 32_000; +const odpManagerSymbol: unique symbol = Symbol(); + +export type OpaqueOdpManager = { + [odpManagerSymbol]: unknown; +}; + export type OdpManagerOptions = { segmentsCache?: Cache<string[]>; segmentsCacheSize?: number; @@ -93,3 +99,13 @@ export const getOdpManager = (options: OdpManagerFactoryOptions): OdpManager => userAgentParser: options.userAgentParser, }); }; + +export const getOpaqueOdpManager = (options: OdpManagerFactoryOptions): OpaqueOdpManager => { + return { + [odpManagerSymbol]: getOdpManager(options), + }; +}; + +export const extractOdpManager = (manager: OpaqueOdpManager): OdpManager => { + return manager[odpManagerSymbol] as OdpManager; +} diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index cb5210915..593cb84ba 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -24,6 +24,7 @@ import { LoggerFacade } from '../logging/logger'; import { createProjectConfig } from '../project_config/project_config'; import { getMockLogger } from '../tests/mock/mock_logger'; import { createOdpManager } from '../odp/odp_manager_factory.node'; +import { extractOdpManager } from '../odp/odp_manager_factory'; describe('Optimizely', () => { const eventDispatcher = { @@ -31,7 +32,7 @@ describe('Optimizely', () => { }; const eventProcessor = getForwardingEventProcessor(eventDispatcher); - const odpManager = createOdpManager({}); + const odpManager = extractOdpManager(createOdpManager({})); const logger = getMockLogger(); it('should pass disposable options to the respective services', () => { diff --git a/lib/project_config/config_manager_factory.browser.spec.ts b/lib/project_config/config_manager_factory.browser.spec.ts index 843111fb4..9dfa7bced 100644 --- a/lib/project_config/config_manager_factory.browser.spec.ts +++ b/lib/project_config/config_manager_factory.browser.spec.ts @@ -19,6 +19,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; vi.mock('./config_manager_factory', () => { return { getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + getOpaquePollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), }; }); @@ -27,17 +28,17 @@ vi.mock('../utils/http_request_handler/request_handler.browser', () => { return { BrowserRequestHandler }; }); -import { getPollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; +import { getOpaquePollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.browser'; import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { - const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); + const mockGetOpaquePollingConfigManager = vi.mocked(getOpaquePollingConfigManager); const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); beforeEach(() => { - mockGetPollingConfigManager.mockClear(); + mockGetOpaquePollingConfigManager.mockClear(); MockBrowserRequestHandler.mockClear(); }); @@ -47,7 +48,7 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(projectConfigManager, mockGetPollingConfigManager.mock.results[0].value)).toBe(true); + expect(Object.is(projectConfigManager, mockGetOpaquePollingConfigManager.mock.results[0].value)).toBe(true); }); it('uses an instance of BrowserRequestHandler as requestHandler', () => { @@ -56,7 +57,7 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0])).toBe(true); + expect(Object.is(mockGetOpaquePollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0])).toBe(true); }); it('uses uses autoUpdate = false by default', () => { @@ -65,7 +66,7 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(false); + expect(mockGetOpaquePollingConfigManager.mock.calls[0][0].autoUpdate).toBe(false); }); it('uses the provided options', () => { @@ -81,6 +82,6 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + expect(mockGetOpaquePollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); }); }); diff --git a/lib/project_config/config_manager_factory.browser.ts b/lib/project_config/config_manager_factory.browser.ts index 8a5433bd5..0a96affd5 100644 --- a/lib/project_config/config_manager_factory.browser.ts +++ b/lib/project_config/config_manager_factory.browser.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { ProjectConfigManager } from './project_config_manager'; -export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => { const defaultConfig = { autoUpdate: false, requestHandler: new BrowserRequestHandler(), }; - return getPollingConfigManager({ ...defaultConfig, ...config }); + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); }; diff --git a/lib/project_config/config_manager_factory.node.spec.ts b/lib/project_config/config_manager_factory.node.spec.ts index 41dedd4f5..c0631a63b 100644 --- a/lib/project_config/config_manager_factory.node.spec.ts +++ b/lib/project_config/config_manager_factory.node.spec.ts @@ -19,6 +19,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; vi.mock('./config_manager_factory', () => { return { getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + getOpaquePollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), }; }); @@ -27,17 +28,17 @@ vi.mock('../utils/http_request_handler/request_handler.node', () => { return { NodeRequestHandler }; }); -import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { getOpaquePollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.node'; import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { - const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); + const mockGetOpaquePollingConfigManager = vi.mocked(getOpaquePollingConfigManager); const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); beforeEach(() => { - mockGetPollingConfigManager.mockClear(); + mockGetOpaquePollingConfigManager.mockClear(); MockNodeRequestHandler.mockClear(); }); @@ -47,7 +48,7 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(projectConfigManager, mockGetPollingConfigManager.mock.results[0].value)).toBe(true); + expect(Object.is(projectConfigManager, mockGetOpaquePollingConfigManager.mock.results[0].value)).toBe(true); }); it('uses an instance of NodeRequestHandler as requestHandler', () => { @@ -56,7 +57,7 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].requestHandler, MockNodeRequestHandler.mock.instances[0])).toBe(true); + expect(Object.is(mockGetOpaquePollingConfigManager.mock.calls[0][0].requestHandler, MockNodeRequestHandler.mock.instances[0])).toBe(true); }); it('uses uses autoUpdate = true by default', () => { @@ -65,7 +66,7 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); + expect(mockGetOpaquePollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); }); it('uses the provided options', () => { @@ -81,6 +82,6 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + expect(mockGetOpaquePollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); }); }); diff --git a/lib/project_config/config_manager_factory.node.ts b/lib/project_config/config_manager_factory.node.ts index 241927c5a..58ac126bc 100644 --- a/lib/project_config/config_manager_factory.node.ts +++ b/lib/project_config/config_manager_factory.node.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; import { NodeRequestHandler } from "../utils/http_request_handler/request_handler.node"; import { ProjectConfigManager } from "./project_config_manager"; import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './constant'; -export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => { const defaultConfig = { autoUpdate: true, requestHandler: new NodeRequestHandler(), }; - return getPollingConfigManager({ ...defaultConfig, ...config }); + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); }; diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index 6550fa0c6..52411861d 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -39,6 +39,7 @@ async function mockRequireAsyncStorage() { vi.mock('./config_manager_factory', () => { return { getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + getOpaquePollingConfigManager: vi.fn().mockRejectedValueOnce({ foo: 'bar' }), }; }); @@ -56,19 +57,19 @@ vi.mock('../utils/cache/async_storage_cache.react_native', async (importOriginal return { AsyncStorageCache: MockAsyncStorageCache }; }); -import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { getOpaquePollingConfigManager, getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.react_native'; import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { getMockSyncCache } from '../tests/mock/mock_cache'; describe('createPollingConfigManager', () => { - const mockGetPollingConfigManager = vi.mocked(getPollingConfigManager); + const mockGetOpaquePollingConfigManager = vi.mocked(getOpaquePollingConfigManager); const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); beforeEach(() => { - mockGetPollingConfigManager.mockClear(); + mockGetOpaquePollingConfigManager.mockClear(); MockBrowserRequestHandler.mockClear(); MockAsyncStorageCache.mockClear(); }); @@ -79,7 +80,7 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(projectConfigManager, mockGetPollingConfigManager.mock.results[0].value)).toBe(true); + expect(Object.is(projectConfigManager, mockGetOpaquePollingConfigManager.mock.results[0].value)).toBe(true); }); it('uses an instance of BrowserRequestHandler as requestHandler', () => { @@ -91,7 +92,7 @@ describe('createPollingConfigManager', () => { expect( Object.is( - mockGetPollingConfigManager.mock.calls[0][0].requestHandler, + mockGetOpaquePollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0] ) ).toBe(true); @@ -104,7 +105,7 @@ describe('createPollingConfigManager', () => { createPollingProjectConfigManager(config); - expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); + expect(mockGetOpaquePollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); }); it('uses an instance of ReactNativeAsyncStorageCache for caching by default', () => { @@ -115,7 +116,7 @@ describe('createPollingConfigManager', () => { createPollingProjectConfigManager(config); expect( - Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockAsyncStorageCache.mock.instances[0]) + Object.is(mockGetOpaquePollingConfigManager.mock.calls[0][0].cache, MockAsyncStorageCache.mock.instances[0]) ).toBe(true); }); @@ -133,7 +134,7 @@ describe('createPollingConfigManager', () => { createPollingProjectConfigManager(config); - expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + expect(mockGetOpaquePollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); }); it('Should not throw error if a cache is present in the config, and async storage is not available', async () => { diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts index 6edcbbe3f..8ea480595 100644 --- a/lib/project_config/config_manager_factory.react_native.ts +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -14,17 +14,19 @@ * limitations under the License. */ -import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { getOpaquePollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; import { BrowserRequestHandler } from "../utils/http_request_handler/request_handler.browser"; import { ProjectConfigManager } from "./project_config_manager"; import { AsyncStorageCache } from "../utils/cache/async_storage_cache.react_native"; -export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { +import { OpaqueConfigManager } from "./config_manager_factory"; + +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => { const defaultConfig = { autoUpdate: true, requestHandler: new BrowserRequestHandler(), cache: config.cache || new AsyncStorageCache(), }; - return getPollingConfigManager({ ...defaultConfig, ...config }); + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); }; diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 141952148..6f01c2589 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -26,6 +26,12 @@ import { StartupLog } from "../service"; import { MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; import { LogLevel } from '../logging/logger' +const configManagerSymbol: unique symbol = Symbol(); + +export type OpaqueConfigManager = { + [configManagerSymbol]: unknown; +}; + export type StaticConfigManagerConfig = { datafile: string, jsonSchemaValidator?: Transformer<unknown, boolean>, @@ -33,8 +39,10 @@ export type StaticConfigManagerConfig = { export const createStaticProjectConfigManager = ( config: StaticConfigManagerConfig -): ProjectConfigManager => { - return new ProjectConfigManagerImpl(config); +): OpaqueConfigManager => { + return { + [configManagerSymbol]: new ProjectConfigManagerImpl(config), + } }; export type PollingConfigManagerConfig = { @@ -87,3 +95,19 @@ export const getPollingConfigManager = ( jsonSchemaValidator: opt.jsonSchemaValidator, }); }; + +export const getOpaquePollingConfigManager = (opt: PollingConfigManagerFactoryOptions): OpaqueConfigManager => { + return { + [configManagerSymbol]: getPollingConfigManager(opt), + }; +}; + +export const wrapConfigManager = (configManager: ProjectConfigManager): OpaqueConfigManager => { + return { + [configManagerSymbol]: configManager, + }; +}; + +export const extractConfigManager = (opaqueConfigManager: OpaqueConfigManager): ProjectConfigManager => { + return opaqueConfigManager[configManagerSymbol] as ProjectConfigManager; +}; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 0cb41e6d0..13bf965cb 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -35,13 +35,16 @@ import { DefaultOdpEventApiManager } from './odp/event_manager/odp_event_api_man import { OdpEventManager } from './odp/event_manager/odp_event_manager'; import { OdpManager } from './odp/odp_manager'; import { ProjectConfig } from './project_config/project_config'; -import { ProjectConfigManager } from './project_config/project_config_manager'; +import { OpaqueConfigManager } from './project_config/config_manager_factory'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor/event_processor'; import { VuidManager } from './vuid/vuid_manager'; import { ErrorNotifier } from './error/error_notifier'; import { OpaqueLogger } from './logging/logger_factory'; import { OpaqueErrorNotifier } from './error/error_notifier_factory'; +import { OpaqueEventProcessor } from './event_processor/event_processor_factory'; +import { OpaqueOdpManager } from './odp/odp_manager_factory'; +import { OpaqueVuidManager } from './vuid/vuid_manager_factory'; export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; export { EventProcessor } from './event_processor/event_processor'; @@ -342,8 +345,8 @@ export interface TrackListenerPayload extends ListenerPayload { * For compatibility with the previous declaration file */ export interface Config { - projectConfigManager: ProjectConfigManager; - eventProcessor?: EventProcessor; + projectConfigManager: OpaqueConfigManager; + eventProcessor?: OpaqueEventProcessor; // The object to validate against the schema jsonSchemaValidator?: { validate(jsonObject: unknown): boolean; @@ -356,8 +359,8 @@ export interface Config { defaultDecideOptions?: OptimizelyDecideOption[]; clientEngine?: string; clientVersion?: string; - odpManager?: OdpManager; - vuidManager?: VuidManager; + odpManager?: OpaqueOdpManager; + vuidManager?: OpaqueVuidManager; disposable?: boolean; } diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 1c867fbbf..573857d00 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -41,8 +41,6 @@ export const CONTROL_ATTRIBUTES = { export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; export const NODE_CLIENT_ENGINE = 'node-sdk'; -export const REACT_CLIENT_ENGINE = 'react-sdk'; -export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; export const CLIENT_VERSION = '5.3.4'; diff --git a/lib/vuid/vuid_manager_factory.browser.spec.ts b/lib/vuid/vuid_manager_factory.browser.spec.ts index d4a7c2c72..805064c4b 100644 --- a/lib/vuid/vuid_manager_factory.browser.spec.ts +++ b/lib/vuid/vuid_manager_factory.browser.spec.ts @@ -33,8 +33,9 @@ import { getMockSyncCache } from '../tests/mock/mock_cache'; import { createVuidManager } from './vuid_manager_factory.browser'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; +import { extractVuidManager } from './vuid_manager_factory'; -describe('createVuidManager', () => { +describe('extractVuidManager(createVuidManager', () => { const MockVuidCacheManager = vi.mocked(VuidCacheManager); const MockLocalStorageCache = vi.mocked(LocalStorageCache); const MockDefaultVuidManager = vi.mocked(DefaultVuidManager); @@ -45,24 +46,24 @@ describe('createVuidManager', () => { }); it('should pass the enableVuid option to the DefaultVuidManager', () => { - const manager = createVuidManager({ enableVuid: true }); + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); expect(MockDefaultVuidManager.mock.calls[0][0].enableVuid).toBe(true); - const manager2 = createVuidManager({ enableVuid: false }); + const manager2 = extractVuidManager(createVuidManager({ enableVuid: false })); expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); expect(MockDefaultVuidManager.mock.calls[1][0].enableVuid).toBe(false); }); it('should use the provided cache', () => { const cache = getMockSyncCache<string>(); - const manager = createVuidManager({ enableVuid: true, vuidCache: cache }); + const manager = extractVuidManager(createVuidManager({ enableVuid: true, vuidCache: cache })); expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); expect(MockDefaultVuidManager.mock.calls[0][0].vuidCache).toBe(cache); }); it('should use a LocalStorageCache if no cache is provided', () => { - const manager = createVuidManager({ enableVuid: true }); + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); const usedCache = MockDefaultVuidManager.mock.calls[0][0].vuidCache; @@ -70,8 +71,8 @@ describe('createVuidManager', () => { }); it('should use a single VuidCacheManager instance for all VuidManager instances', () => { - const manager1 = createVuidManager({ enableVuid: true }); - const manager2 = createVuidManager({ enableVuid: true }); + const manager1 = extractVuidManager(createVuidManager({ enableVuid: true })); + const manager2 = extractVuidManager(createVuidManager({ enableVuid: true })); expect(manager1).toBe(MockDefaultVuidManager.mock.instances[0]); expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); expect(MockVuidCacheManager.mock.instances.length).toBe(1); diff --git a/lib/vuid/vuid_manager_factory.browser.ts b/lib/vuid/vuid_manager_factory.browser.ts index cf8df6a44..97e94dc2e 100644 --- a/lib/vuid/vuid_manager_factory.browser.ts +++ b/lib/vuid/vuid_manager_factory.browser.ts @@ -15,14 +15,14 @@ */ import { DefaultVuidManager, VuidCacheManager, VuidManager } from './vuid_manager'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; -import { VuidManagerOptions } from './vuid_manager_factory'; +import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_manager_factory'; export const vuidCacheManager = new VuidCacheManager(); -export const createVuidManager = (options: VuidManagerOptions): VuidManager => { - return new DefaultVuidManager({ +export const createVuidManager = (options: VuidManagerOptions): OpaqueVuidManager => { + return wrapVuidManager(new DefaultVuidManager({ vuidCacheManager, vuidCache: options.vuidCache || new LocalStorageCache<string>(), enableVuid: options.enableVuid - }); + })); } diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts index ebc7fd373..e8de3e564 100644 --- a/lib/vuid/vuid_manager_factory.node.ts +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -14,11 +14,10 @@ * limitations under the License. */ import { VuidManager } from './vuid_manager'; -import { VuidManagerOptions } from './vuid_manager_factory'; +import { OpaqueVuidManager, VuidManagerOptions } from './vuid_manager_factory'; export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; -export const createVuidManager = (options: VuidManagerOptions): VuidManager => { +export const createVuidManager = (options: VuidManagerOptions): OpaqueVuidManager => { throw new Error(VUID_IS_NOT_SUPPORTED_IN_NODEJS); }; - diff --git a/lib/vuid/vuid_manager_factory.react_native.spec.ts b/lib/vuid/vuid_manager_factory.react_native.spec.ts index 22920c099..8057946e3 100644 --- a/lib/vuid/vuid_manager_factory.react_native.spec.ts +++ b/lib/vuid/vuid_manager_factory.react_native.spec.ts @@ -34,8 +34,9 @@ import { createVuidManager } from './vuid_manager_factory.react_native'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; +import { extractVuidManager } from './vuid_manager_factory'; -describe('createVuidManager', () => { +describe('extractVuidManager(createVuidManager', () => { const MockVuidCacheManager = vi.mocked(VuidCacheManager); const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); const MockDefaultVuidManager = vi.mocked(DefaultVuidManager); @@ -46,24 +47,24 @@ describe('createVuidManager', () => { }); it('should pass the enableVuid option to the DefaultVuidManager', () => { - const manager = createVuidManager({ enableVuid: true }); + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); expect(MockDefaultVuidManager.mock.calls[0][0].enableVuid).toBe(true); - const manager2 = createVuidManager({ enableVuid: false }); + const manager2 = extractVuidManager(createVuidManager({ enableVuid: false })); expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); expect(MockDefaultVuidManager.mock.calls[1][0].enableVuid).toBe(false); }); it('should use the provided cache', () => { const cache = getMockAsyncCache<string>(); - const manager = createVuidManager({ enableVuid: true, vuidCache: cache }); + const manager = extractVuidManager(createVuidManager({ enableVuid: true, vuidCache: cache })); expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); expect(MockDefaultVuidManager.mock.calls[0][0].vuidCache).toBe(cache); }); it('should use a AsyncStorageCache if no cache is provided', () => { - const manager = createVuidManager({ enableVuid: true }); + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); const usedCache = MockDefaultVuidManager.mock.calls[0][0].vuidCache; @@ -71,8 +72,8 @@ describe('createVuidManager', () => { }); it('should use a single VuidCacheManager instance for all VuidManager instances', () => { - const manager1 = createVuidManager({ enableVuid: true }); - const manager2 = createVuidManager({ enableVuid: true }); + const manager1 = extractVuidManager(createVuidManager({ enableVuid: true })); + const manager2 = extractVuidManager(createVuidManager({ enableVuid: true })); expect(manager1).toBe(MockDefaultVuidManager.mock.instances[0]); expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); expect(MockVuidCacheManager.mock.instances.length).toBe(1); diff --git a/lib/vuid/vuid_manager_factory.react_native.ts b/lib/vuid/vuid_manager_factory.react_native.ts index 6eba4c9f2..51b3f754b 100644 --- a/lib/vuid/vuid_manager_factory.react_native.ts +++ b/lib/vuid/vuid_manager_factory.react_native.ts @@ -15,14 +15,14 @@ */ import { DefaultVuidManager, VuidCacheManager, VuidManager } from './vuid_manager'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; -import { VuidManagerOptions } from './vuid_manager_factory'; +import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_manager_factory'; export const vuidCacheManager = new VuidCacheManager(); -export const createVuidManager = (options: VuidManagerOptions): VuidManager => { - return new DefaultVuidManager({ +export const createVuidManager = (options: VuidManagerOptions): OpaqueVuidManager => { + return wrapVuidManager(new DefaultVuidManager({ vuidCacheManager, vuidCache: options.vuidCache || new AsyncStorageCache<string>(), enableVuid: options.enableVuid - }); + })); } diff --git a/lib/vuid/vuid_manager_factory.ts b/lib/vuid/vuid_manager_factory.ts index ab2264242..61ac36966 100644 --- a/lib/vuid/vuid_manager_factory.ts +++ b/lib/vuid/vuid_manager_factory.ts @@ -15,8 +15,25 @@ */ import { Cache } from '../utils/cache/cache'; +import { VuidManager } from './vuid_manager'; export type VuidManagerOptions = { vuidCache?: Cache<string>; enableVuid?: boolean; } + +const vuidManagerSymbol: unique symbol = Symbol(); + +export type OpaqueVuidManager = { + [vuidManagerSymbol]: unknown; +}; + +export const extractVuidManager = (opaqueVuidManager: OpaqueVuidManager): VuidManager => { + return opaqueVuidManager[vuidManagerSymbol] as VuidManager; +}; + +export const wrapVuidManager = (vuidManager: VuidManager): OpaqueVuidManager => { + return { + [vuidManagerSymbol]: vuidManager + } +}; diff --git a/package.json b/package.json index 2d97998df..e7a8fbd77 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "clean": "rm -rf dist", "clean:win": "(if exist dist rd /s/q dist)", "lint": "tsc --noEmit && eslint 'lib/**/*.js' 'lib/**/*.ts'", - "test-vitest": "tsc --noEmit --p tsconfig.spec.json && vitest run", + "test-vitest": "vitest run", "test-mocha": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r tsconfig-paths/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js'", "test": "npm run test-mocha && npm run test-vitest", "posttest": "npm run lint", diff --git a/vitest.config.mts b/vitest.config.mts index 05669feb1..584eeb60d 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -28,6 +28,7 @@ export default defineConfig({ environment: 'happy-dom', include: ['**/*.spec.ts'], typecheck: { + enabled: true, tsconfig: 'tsconfig.spec.json', }, }, From 49c6b8a7c9b291cf9e1c5021f889c481759b50e5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 18 Feb 2025 23:06:57 +0600 Subject: [PATCH 125/200] [FSSDK-11114] add type test for entrypoints (#1004) --- lib/common_exports.ts | 23 +++++++++- lib/entrypoint.test-d.ts | 92 ++++++++++++++++++++++++++++++++++----- lib/export_types.ts | 60 ++++++++++++++++++++++--- lib/index.browser.ts | 52 ++++++---------------- lib/index.node.ts | 50 +++++++-------------- lib/index.react_native.ts | 46 +++++++------------- lib/shared_types.ts | 1 - 7 files changed, 201 insertions(+), 123 deletions(-) diff --git a/lib/common_exports.ts b/lib/common_exports.ts index 583f1b455..947a3bcb4 100644 --- a/lib/common_exports.ts +++ b/lib/common_exports.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023-2024 Optimizely + * Copyright 2023-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,23 @@ */ export { createStaticProjectConfigManager } from './project_config/config_manager_factory'; -export { PollingConfigManagerConfig } from './project_config/config_manager_factory'; + +export { LogLevel } from './logging/logger'; + +export { + DebugLog, + InfoLog, + WarnLog, + ErrorLog, +} from './logging/logger_factory'; + +export { createLogger } from './logging/logger_factory'; +export { createErrorNotifier } from './error/error_notifier_factory'; + +export { + DECISION_SOURCES, + DECISION_NOTIFICATION_TYPES, + NOTIFICATION_TYPES, +} from './utils/enums'; + +export { OptimizelyDecideOption } from './shared_types'; diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts index f408688b2..393a7cdb8 100644 --- a/lib/entrypoint.test-d.ts +++ b/lib/entrypoint.test-d.ts @@ -16,23 +16,91 @@ import { expectTypeOf } from 'vitest'; -import * as browserEntrypoint from './index.browser'; -import * as nodeEntrypoint from './index.node'; -import * as reactNativeEntrypoint from './index.react_native'; +import * as browser from './index.browser'; +import * as node from './index.node'; +import * as reactNative from './index.react_native'; -import { Config, Client } from './shared_types'; +type WithoutReadonly<T> = { -readonly [P in keyof T]: T[P] }; + +const nodeEntrypoint: WithoutReadonly<typeof node> = node; +const browserEntrypoint: WithoutReadonly<typeof browser> = browser; +const reactNativeEntrypoint: WithoutReadonly<typeof reactNative> = reactNative; + +import { + Config, + Client, + StaticConfigManagerConfig, + OpaqueConfigManager, + PollingConfigManagerConfig, + EventDispatcher, + OpaqueEventProcessor, + BatchEventProcessorOptions, + OdpManagerOptions, + OpaqueOdpManager, + VuidManagerOptions, + OpaqueVuidManager, + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, + ErrorHandler, + OpaqueErrorNotifier, +} from './export_types'; + +import { + DECISION_SOURCES, + DECISION_NOTIFICATION_TYPES, + NOTIFICATION_TYPES, +} from './utils/enums'; + +import { LogLevel } from './logging/logger'; + +import { OptimizelyDecideOption } from './shared_types'; export type Entrypoint = { + // client factory createInstance: (config: Config) => Client | null; -} + // config manager related exports + createStaticProjectConfigManager: (config: StaticConfigManagerConfig) => OpaqueConfigManager; + createPollingProjectConfigManager: (config: PollingConfigManagerConfig) => OpaqueConfigManager; + + // event processor related exports + eventDispatcher: EventDispatcher; + getSendBeaconEventDispatcher: () => EventDispatcher; + createForwardingEventProcessor: (eventDispatcher?: EventDispatcher) => OpaqueEventProcessor; + createBatchEventProcessor: (options: BatchEventProcessorOptions) => OpaqueEventProcessor; + + // odp manager related exports + createOdpManager: (options: OdpManagerOptions) => OpaqueOdpManager; + + // vuid manager related exports + createVuidManager: (options: VuidManagerOptions) => OpaqueVuidManager; + + // logger related exports + LogLevel: typeof LogLevel; + DebugLog: OpaqueLevelPreset, + InfoLog: OpaqueLevelPreset, + WarnLog: OpaqueLevelPreset, + ErrorLog: OpaqueLevelPreset, + createLogger: (config: LoggerConfig) => OpaqueLogger; + + // error related exports + createErrorNotifier: (errorHandler: ErrorHandler) => OpaqueErrorNotifier; + + // enums + DECISION_SOURCES: typeof DECISION_SOURCES; + DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; + NOTIFICATION_TYPES: typeof NOTIFICATION_TYPES; + + // decide options + OptimizelyDecideOption: typeof OptimizelyDecideOption; +} -// these type tests will be fixed in a future PR -// expectTypeOf(browserEntrypoint).toEqualTypeOf<Entrypoint>(); -// expectTypeOf(nodeEntrypoint).toEqualTypeOf<Entrypoint>(); -// expectTypeOf(reactNativeEntrypoint).toEqualTypeOf<Entrypoint>(); +expectTypeOf(browserEntrypoint).toEqualTypeOf<Entrypoint>(); +expectTypeOf(nodeEntrypoint).toEqualTypeOf<Entrypoint>(); +expectTypeOf(reactNativeEntrypoint).toEqualTypeOf<Entrypoint>(); -// expectTypeOf(browserEntrypoint).toEqualTypeOf(nodeEntrypoint); -// expectTypeOf(browserEntrypoint).toEqualTypeOf(reactNativeEntrypoint); -// expectTypeOf(nodeEntrypoint).toEqualTypeOf(reactNativeEntrypoint); +expectTypeOf(browserEntrypoint).toEqualTypeOf(nodeEntrypoint); +expectTypeOf(browserEntrypoint).toEqualTypeOf(reactNativeEntrypoint); +expectTypeOf(nodeEntrypoint).toEqualTypeOf(reactNativeEntrypoint); diff --git a/lib/export_types.ts b/lib/export_types.ts index 84bda50c7..be8b77254 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -14,11 +14,62 @@ * limitations under the License. */ -/** - * This file contains a collection of all types to be externally exported. - */ +// config manager related types +export type { + StaticConfigManagerConfig, + PollingConfigManagerConfig, + OpaqueConfigManager, +} from './project_config/config_manager_factory'; + +// event processor related types +export type { + LogEvent, + EventDispatcherResponse, + EventDispatcher, +} from './event_processor/event_dispatcher/event_dispatcher'; + +export type { + BatchEventProcessorOptions, + OpaqueEventProcessor, +} from './event_processor/event_processor_factory'; + +// Odp manager related types +export type { + OdpManagerOptions, + OpaqueOdpManager, +} from './odp/odp_manager_factory'; + +// Vuid manager related types +export type { + VuidManagerOptions, + OpaqueVuidManager, +} from './vuid/vuid_manager_factory'; -export { +// Logger related types +export type { + LogHandler, +} from './logging/logger'; + +export type { + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, +} from './logging/logger_factory'; + +// Error related types +export type { ErrorHandler } from './error/error_handler'; +export type { OpaqueErrorNotifier } from './error/error_notifier_factory'; + +export type { Cache } from './utils/cache/cache'; + +export type { + NotificationType, + NotificationPayload, +} from './notification_center/type'; + +export type { OptimizelyDecideOption } from './shared_types'; + +export type { UserAttributeValue, UserAttributes, OptimizelyConfig, @@ -31,7 +82,6 @@ export { OptimizelyForcedDecision, EventTags, Event, - EventDispatcher, DatafileOptions, UserProfileService, UserProfile, diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 4190a4b9f..8be972be3 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2019-2024 Optimizely + * Copyright 2016-2017, 2019-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,28 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import configValidator from './utils/config_validator'; -import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; +import { Config, Client } from './shared_types'; import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; -import * as enums from './utils/enums'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import Optimizely from './optimizely'; -import { UserAgentParser } from './odp/ua_parser/user_agent_parser'; -import { getUserAgentParser } from './odp/ua_parser/ua_parser.browser'; -import * as commonExports from './common_exports'; -import { PollingConfigManagerConfig } from './project_config/config_manager_factory'; -import { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; -import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.browser'; -import { createVuidManager } from './vuid/vuid_manager_factory.browser'; -import { createOdpManager } from './odp/odp_manager_factory.browser'; -import { UNABLE_TO_ATTACH_UNLOAD } from 'error_message'; -import { extractLogger, createLogger } from './logging/logger_factory'; -import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; -import { LoggerFacade } from './logging/logger'; -import { Maybe } from './utils/type'; import { getOptimizelyInstance } from './client_factory'; - +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; /** * Creates an instance of the Optimizely class @@ -42,7 +24,7 @@ import { getOptimizelyInstance } from './client_factory'; * @return {Client|null} the Optimizely client object * null on error */ -const createInstance = function(config: Config): Client | null { +export const createInstance = function(config: Config): Client | null { const client = getOptimizelyInstance(config); if (client) { @@ -58,24 +40,18 @@ const createInstance = function(config: Config): Client | null { return client; }; - -export { - defaultEventDispatcher as eventDispatcher, - // sendBeaconEventDispatcher, - enums, - createInstance, - OptimizelyDecideOption, - UserAgentParser as IUserAgentParser, - getUserAgentParser, - createPollingProjectConfigManager, - createForwardingEventProcessor, - createBatchEventProcessor, - createOdpManager, - createVuidManager, - createLogger, - createErrorNotifier, +export const getSendBeaconEventDispatcher = (): EventDispatcher => { + return sendBeaconEventDispatcher; }; +export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.browser'; + +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.browser'; + +export { createOdpManager } from './odp/odp_manager_factory.browser'; +export { createVuidManager } from './vuid/vuid_manager_factory.browser'; + export * from './common_exports'; export * from './export_types'; diff --git a/lib/index.node.ts b/lib/index.node.ts index d3959c75c..e39501e83 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2019-2024 Optimizely + * Copyright 2016-2017, 2019-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import * as enums from './utils/enums'; -import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.node'; -import { createNotificationCenter } from './notification_center'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import * as commonExports from './common_exports'; -import { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; -import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; -import { createVuidManager } from './vuid/vuid_manager_factory.node'; -import { createOdpManager } from './odp/odp_manager_factory.node'; -import { extractLogger, createLogger } from './logging/logger_factory'; -import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; -import { Maybe } from './utils/type'; -import { LoggerFacade } from './logging/logger'; -import { ErrorNotifier } from './error/error_notifier'; +import { NODE_CLIENT_ENGINE } from './utils/enums'; +import { Client, Config } from './shared_types'; import { getOptimizelyInstance } from './client_factory'; +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; /** * Creates an instance of the Optimizely class @@ -36,33 +24,27 @@ import { getOptimizelyInstance } from './client_factory'; * @return {Client|null} the Optimizely client object * null on error */ -const createInstance = function(config: Config): Client | null { +export const createInstance = function(config: Config): Client | null { const nodeConfig = { ...config, - clientEnging: config.clientEngine || enums.NODE_CLIENT_ENGINE, + clientEnging: config.clientEngine || NODE_CLIENT_ENGINE, } return getOptimizelyInstance(nodeConfig); }; -/** - * Entry point into the Optimizely Node testing SDK - */ -export { - defaultEventDispatcher as eventDispatcher, - enums, - createInstance, - OptimizelyDecideOption, - createPollingProjectConfigManager, - createForwardingEventProcessor, - createBatchEventProcessor, - createOdpManager, - createVuidManager, - createLogger, - createErrorNotifier, +export const getSendBeaconEventDispatcher = function(): EventDispatcher { + throw new Error('Send beacon event dispatcher is not supported in NodeJS'); }; -export * from './common_exports'; +export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.node'; +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; + +export { createOdpManager } from './odp/odp_manager_factory.node'; +export { createVuidManager } from './vuid/vuid_manager_factory.node'; + +export * from './common_exports'; export * from './export_types'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index f135d40ba..4cf20fccd 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2024, Optimizely + * Copyright 2019-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,25 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Optimizely from './optimizely'; -import configValidator from './utils/config_validator'; -import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; -import { createNotificationCenter } from './notification_center'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import * as commonExports from './common_exports'; -import { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; -import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor/event_processor_factory.react_native'; -import { createOdpManager } from './odp/odp_manager_factory.react_native'; -import { createVuidManager } from './vuid/vuid_manager_factory.react_native'; - import 'fast-text-encoding'; import 'react-native-get-random-values'; -import { Maybe } from './utils/type'; -import { LoggerFacade } from './logging/logger'; -import { extractLogger, createLogger } from './logging/logger_factory'; -import { extractErrorNotifier, createErrorNotifier } from './error/error_notifier_factory'; + +import { Client, Config } from './shared_types'; import { getOptimizelyInstance } from './client_factory'; import { REACT_NATIVE_JS_CLIENT_ENGINE } from './utils/enums'; +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; /** * Creates an instance of the Optimizely class @@ -39,7 +27,7 @@ import { REACT_NATIVE_JS_CLIENT_ENGINE } from './utils/enums'; * @return {Client|null} the Optimizely client object * null on error */ -const createInstance = function(config: Config): Client | null { +export const createInstance = function(config: Config): Client | null { const rnConfig = { ...config, clientEngine: config.clientEngine || REACT_NATIVE_JS_CLIENT_ENGINE, @@ -48,22 +36,18 @@ const createInstance = function(config: Config): Client | null { return getOptimizelyInstance(rnConfig); }; -/** - * Entry point into the Optimizely Javascript SDK for React Native - */ -export { - defaultEventDispatcher as eventDispatcher, - createInstance, - OptimizelyDecideOption, - createPollingProjectConfigManager, - createForwardingEventProcessor, - createBatchEventProcessor, - createOdpManager, - createVuidManager, - createLogger, - createErrorNotifier, +export const getSendBeaconEventDispatcher = function(): EventDispatcher { + throw new Error('Send beacon event dispatcher is not supported in React Native'); }; +export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.browser'; + +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.react_native'; + +export { createOdpManager } from './odp/odp_manager_factory.react_native'; +export { createVuidManager } from './vuid/vuid_manager_factory.react_native'; + export * from './common_exports'; export * from './export_types'; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 13bf965cb..40ad29a1f 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -324,7 +324,6 @@ export interface Client { onReady(options?: { timeout?: number }): Promise<unknown>; close(): Promise<unknown>; sendOdpEvent(action: string, type?: string, identifiers?: Map<string, string>, data?: Map<string, unknown>): void; - getProjectConfig(): ProjectConfig | null; isOdpIntegrated(): boolean; } From 611453394f1fdd782b53c586a8ad3d2ebe065b2e Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:31:53 +0600 Subject: [PATCH 126/200] Junaed/fssdk 1119 test js to ts (#1002) --- .../bucketer/bucket_value_generator.spec.ts | 43 + lib/core/bucketer/bucket_value_generator.ts | 40 + lib/core/bucketer/index.spec.ts | 391 +++++ lib/core/bucketer/index.tests.js | 22 +- lib/core/bucketer/index.ts | 32 +- .../index.spec.ts | 1411 +++++++++++++++++ lib/core/decision/index.spec.ts | 128 ++ lib/core/decision_service/index.tests.js | 5 +- lib/project_config/optimizely_config.spec.ts | 2 +- lib/project_config/project_config.spec.ts | 2 +- lib/tests/test_data.ts | 6 + 11 files changed, 2039 insertions(+), 43 deletions(-) create mode 100644 lib/core/bucketer/bucket_value_generator.spec.ts create mode 100644 lib/core/bucketer/bucket_value_generator.ts create mode 100644 lib/core/bucketer/index.spec.ts create mode 100644 lib/core/custom_attribute_condition_evaluator/index.spec.ts create mode 100644 lib/core/decision/index.spec.ts diff --git a/lib/core/bucketer/bucket_value_generator.spec.ts b/lib/core/bucketer/bucket_value_generator.spec.ts new file mode 100644 index 000000000..a7662e1f0 --- /dev/null +++ b/lib/core/bucketer/bucket_value_generator.spec.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it } from 'vitest'; +import { sprintf } from '../../utils/fns'; +import { generateBucketValue } from './bucket_value_generator'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { INVALID_BUCKETING_ID } from 'error_message'; + +describe('generateBucketValue', () => { + it('should return a bucket value for different inputs', () => { + const experimentId = 1886780721; + const bucketingKey1 = sprintf('%s%s', 'ppid1', experimentId); + const bucketingKey2 = sprintf('%s%s', 'ppid2', experimentId); + const bucketingKey3 = sprintf('%s%s', 'ppid2', 1886780722); + const bucketingKey4 = sprintf('%s%s', 'ppid3', experimentId); + + expect(generateBucketValue(bucketingKey1)).toBe(5254); + expect(generateBucketValue(bucketingKey2)).toBe(4299); + expect(generateBucketValue(bucketingKey3)).toBe(2434); + expect(generateBucketValue(bucketingKey4)).toBe(5439); + }); + + it('should return an error if it cannot generate the hash value', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => generateBucketValue(null)).toThrowError( + new OptimizelyError(INVALID_BUCKETING_ID) + ); + }); +}); diff --git a/lib/core/bucketer/bucket_value_generator.ts b/lib/core/bucketer/bucket_value_generator.ts new file mode 100644 index 000000000..c5f85303b --- /dev/null +++ b/lib/core/bucketer/bucket_value_generator.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import murmurhash from 'murmurhash'; +import { INVALID_BUCKETING_ID } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +const HASH_SEED = 1; +const MAX_HASH_VALUE = Math.pow(2, 32); +const MAX_TRAFFIC_VALUE = 10000; + +/** + * Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE) + * @param {string} bucketingKey String value for bucketing + * @return {number} The generated bucket value + * @throws If bucketing value is not a valid string + */ +export const generateBucketValue = function(bucketingKey: string): number { + try { + // NOTE: the mmh library already does cast the hash value as an unsigned 32bit int + // https://github.com/perezd/node-murmurhash/blob/master/murmurhash.js#L115 + const hashValue = murmurhash.v3(bucketingKey, HASH_SEED); + const ratio = hashValue / MAX_HASH_VALUE; + return Math.floor(ratio * MAX_TRAFFIC_VALUE); + } catch (ex) { + throw new OptimizelyError(INVALID_BUCKETING_ID, bucketingKey, ex.message); + } +}; diff --git a/lib/core/bucketer/index.spec.ts b/lib/core/bucketer/index.spec.ts new file mode 100644 index 000000000..36f23b2eb --- /dev/null +++ b/lib/core/bucketer/index.spec.ts @@ -0,0 +1,391 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; +import { sprintf } from '../../utils/fns'; +import projectConfig, { ProjectConfig } from '../../project_config/project_config'; +import { getTestProjectConfig } from '../../tests/test_data'; +import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from 'error_message'; +import * as bucketer from './'; +import * as bucketValueGenerator from './bucket_value_generator'; + +import { + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_IN_ANY_EXPERIMENT, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, +} from '.'; +import { BucketerParams } from '../../shared_types'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { LoggerFacade } from '../../logging/logger'; + +const testData = getTestProjectConfig(); + +function cloneDeep<T>(value: T): T { + if (value === null || typeof value !== 'object') { + return value; + } + + if (Array.isArray(value)) { + return (value.map(cloneDeep) as unknown) as T; + } + + const copy: Record<string, unknown> = {}; + + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + copy[key] = cloneDeep((value as Record<string, unknown>)[key]); + } + } + + return copy as T; +} + +const setLogSpy = (logger: LoggerFacade) => { + vi.spyOn(logger, 'info'); + vi.spyOn(logger, 'debug'); + vi.spyOn(logger, 'warn'); + vi.spyOn(logger, 'error'); +}; + +describe('excluding groups', () => { + let configObj; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[0].id, + experimentKey: configObj.experiments[0].key, + trafficAllocationConfig: configObj.experiments[0].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + }; + + vi.spyOn(bucketValueGenerator, 'generateBucketValue') + .mockReturnValueOnce(50) + .mockReturnValueOnce(50000); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with correct variation ID when provided bucket value', async () => { + const bucketerParamsTest1 = cloneDeep(bucketerParams); + bucketerParamsTest1.userId = 'ppid1'; + const decisionResponse = bucketer.bucket(bucketerParamsTest1); + + expect(decisionResponse.result).toBe('111128'); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'ppid1'); + + const bucketerParamsTest2 = cloneDeep(bucketerParams); + bucketerParamsTest2.userId = 'ppid2'; + const decisionResponse2 = bucketer.bucket(bucketerParamsTest2); + + expect(decisionResponse2.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'ppid2'); + }); +}); + +describe('including groups: random', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[4].id, + experimentKey: configObj.experiments[4].key, + trafficAllocationConfig: configObj.experiments[4].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + userId: 'testUser', + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with the proper variation for a user in a grouped experiment', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue') + .mockReturnValueOnce(50) + .mockReturnValueOnce(50); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBe('551'); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledTimes(2); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith( + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + 'testUser', + 'groupExperiment1', + '666' + ); + }); + + it('should return decision response with variation null when a user is bucketed into a different grouped experiment than the one speicfied', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValue(5000); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith( + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + 'testUser', + 'groupExperiment1', + '666' + ); + }); + + it('should return decision response with variation null when a user is not bucketed into any experiments in the random group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValue(50000); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith(USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666'); + }); + + it('should return decision response with variation null when a user is bucketed into traffic space of deleted experiment within a random group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(9000); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith(USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666'); + }); + + it('should throw an error if group ID is not in the datafile', () => { + const bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); + bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; + + expect(() => bucketer.bucket(bucketerParamsWithInvalidGroupId)).toThrowError( + new OptimizelyError(INVALID_GROUP_ID, '6969') + ); + }); +}); + +describe('including groups: overlapping', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[6].id, + experimentKey: configObj.experiments[6].key, + trafficAllocationConfig: configObj.experiments[6].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + userId: 'testUser', + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with variation when a user falls into an experiment within an overlapping group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(0); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBe('553'); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + }); + + it('should return decision response with variation null when a user does not fall into an experiment within an overlapping group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(3000); + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + }); +}); + +describe('bucket value falls into empty traffic allocation ranges', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[0].id, + experimentKey: configObj.experiments[0].key, + trafficAllocationConfig: [ + { + entityId: '', + endOfRange: 5000, + }, + { + entityId: '', + endOfRange: 10000, + }, + ], + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with variation null', () => { + const bucketerParamsTest1 = cloneDeep(bucketerParams); + bucketerParamsTest1.userId = 'ppid1'; + const decisionResponse = bucketer.bucket(bucketerParamsTest1); + + expect(decisionResponse.result).toBeNull(); + }); + + it('should not log an invalid variation ID warning', () => { + bucketer.bucket(bucketerParams); + + expect(mockLogger.warn).not.toHaveBeenCalled(); + }); +}); + +describe('traffic allocation has invalid variation ids', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + bucketerParams = { + experimentId: configObj.experiments[0].id, + experimentKey: configObj.experiments[0].key, + trafficAllocationConfig: [ + { + entityId: '-1', + endOfRange: 5000, + }, + { + entityId: '-2', + endOfRange: 10000, + }, + ], + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with variation null', () => { + const bucketerParamsTest1 = cloneDeep(bucketerParams); + bucketerParamsTest1.userId = 'ppid1'; + const decisionResponse = bucketer.bucket(bucketerParamsTest1); + + expect(decisionResponse.result).toBeNull(); + }); +}); + + + +describe('testBucketWithBucketingId', () => { + let bucketerParams: BucketerParams; + + beforeEach(() => { + const configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + trafficAllocationConfig: configObj.experiments[0].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + }; + }); + + it('check that a non null bucketingId buckets a variation different than the one expected with userId', () => { + const bucketerParams1 = cloneDeep(bucketerParams); + bucketerParams1['userId'] = 'testBucketingIdControl'; + bucketerParams1['bucketingId'] = '123456789'; + bucketerParams1['experimentKey'] = 'testExperiment'; + bucketerParams1['experimentId'] = '111127'; + + expect(bucketer.bucket(bucketerParams1).result).toBe('111129'); + }); + + it('check that a null bucketing ID defaults to bucketing with the userId', () => { + const bucketerParams2 = cloneDeep(bucketerParams); + bucketerParams2['userId'] = 'testBucketingIdControl'; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams2['bucketingId'] = null; + bucketerParams2['experimentKey'] = 'testExperiment'; + bucketerParams2['experimentId'] = '111127'; + + expect(bucketer.bucket(bucketerParams2).result).toBe('111128'); + }); + + it('check that bucketing works with an experiment in group', () => { + const bucketerParams4 = cloneDeep(bucketerParams); + bucketerParams4['userId'] = 'testBucketingIdControl'; + bucketerParams4['bucketingId'] = '123456789'; + bucketerParams4['experimentKey'] = 'groupExperiment2'; + bucketerParams4['experimentId'] = '443'; + + expect(bucketer.bucket(bucketerParams4).result).toBe('111128'); + }); +}); diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index 023431af7..0bdf62f4a 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -17,7 +17,7 @@ import sinon from 'sinon'; import { assert, expect } from 'chai'; import { cloneDeep, create } from 'lodash'; import { sprintf } from '../../utils/fns'; - +import * as bucketValueGenerator from './bucket_value_generator' import * as bucketer from './'; import { LOG_LEVEL } from '../../utils/enums'; import projectConfig from '../../project_config/project_config'; @@ -76,7 +76,7 @@ describe('lib/core/bucketer', function () { logger: createdLogger, }; sinon - .stub(bucketer, '_generateBucketValue') + .stub(bucketValueGenerator, 'generateBucketValue') .onFirstCall() .returns(50) .onSecondCall() @@ -84,7 +84,7 @@ describe('lib/core/bucketer', function () { }); afterEach(function () { - bucketer._generateBucketValue.restore(); + bucketValueGenerator.generateBucketValue.restore(); }); it('should return decision response with correct variation ID when provided bucket value', function () { @@ -116,11 +116,11 @@ describe('lib/core/bucketer', function () { groupIdMap: configObj.groupIdMap, logger: createdLogger, }; - bucketerStub = sinon.stub(bucketer, '_generateBucketValue'); + bucketerStub = sinon.stub(bucketValueGenerator, 'generateBucketValue'); }); afterEach(function () { - bucketer._generateBucketValue.restore(); + bucketValueGenerator.generateBucketValue.restore(); }); describe('random groups', function () { @@ -328,7 +328,7 @@ describe('lib/core/bucketer', function () { }); }); - describe('_generateBucketValue', function () { + describe('generateBucketValue', function () { it('should return a bucket value for different inputs', function () { var experimentId = 1886780721; var bucketingKey1 = sprintf('%s%s', 'ppid1', experimentId); @@ -336,15 +336,15 @@ describe('lib/core/bucketer', function () { var bucketingKey3 = sprintf('%s%s', 'ppid2', 1886780722); var bucketingKey4 = sprintf('%s%s', 'ppid3', experimentId); - expect(bucketer._generateBucketValue(bucketingKey1)).to.equal(5254); - expect(bucketer._generateBucketValue(bucketingKey2)).to.equal(4299); - expect(bucketer._generateBucketValue(bucketingKey3)).to.equal(2434); - expect(bucketer._generateBucketValue(bucketingKey4)).to.equal(5439); + expect(bucketValueGenerator.generateBucketValue(bucketingKey1)).to.equal(5254); + expect(bucketValueGenerator.generateBucketValue(bucketingKey2)).to.equal(4299); + expect(bucketValueGenerator.generateBucketValue(bucketingKey3)).to.equal(2434); + expect(bucketValueGenerator.generateBucketValue(bucketingKey4)).to.equal(5439); }); it('should return an error if it cannot generate the hash value', function() { const response = assert.throws(function() { - bucketer._generateBucketValue(null); + bucketValueGenerator.generateBucketValue(null); } ); expect(response.baseMessage).to.equal(INVALID_BUCKETING_ID); }); diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index d965e0217..b2455b95a 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -17,7 +17,6 @@ /** * Bucketer API for determining the variation id from the specified parameters */ -import murmurhash from 'murmurhash'; import { LoggerFacade } from '../../logging/logger'; import { DecisionResponse, @@ -25,19 +24,15 @@ import { TrafficAllocation, Group, } from '../../shared_types'; - -import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from 'error_message'; +import { INVALID_GROUP_ID } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; +import { generateBucketValue } from './bucket_value_generator'; export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.'; export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.'; export const USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is in experiment %s of group %s.'; export const USER_ASSIGNED_TO_EXPERIMENT_BUCKET = 'Assigned bucket %s to user with bucketing ID %s.'; export const INVALID_VARIATION_ID = 'Bucketed into an invalid variation ID. Returning null.'; - -const HASH_SEED = 1; -const MAX_HASH_VALUE = Math.pow(2, 32); -const MAX_TRAFFIC_VALUE = 10000; const RANDOM_POLICY = 'random'; /** @@ -128,7 +123,7 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse } } const bucketingId = `${bucketerParams.bucketingId}${bucketerParams.experimentId}`; - const bucketValue = _generateBucketValue(bucketingId); + const bucketValue = generateBucketValue(bucketingId); bucketerParams.logger?.debug( USER_ASSIGNED_TO_EXPERIMENT_BUCKET, @@ -176,7 +171,7 @@ export const bucketUserIntoExperiment = function( logger?: LoggerFacade ): string | null { const bucketingKey = `${bucketingId}${group.id}`; - const bucketValue = _generateBucketValue(bucketingKey); + const bucketValue = generateBucketValue(bucketingKey); logger?.debug( USER_ASSIGNED_TO_EXPERIMENT_BUCKET, bucketValue, @@ -208,26 +203,7 @@ export const _findBucket = function( return null; }; -/** - * Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE) - * @param {string} bucketingKey String value for bucketing - * @return {number} The generated bucket value - * @throws If bucketing value is not a valid string - */ -export const _generateBucketValue = function(bucketingKey: string): number { - try { - // NOTE: the mmh library already does cast the hash value as an unsigned 32bit int - // https://github.com/perezd/node-murmurhash/blob/master/murmurhash.js#L115 - const hashValue = murmurhash.v3(bucketingKey, HASH_SEED); - const ratio = hashValue / MAX_HASH_VALUE; - return Math.floor(ratio * MAX_TRAFFIC_VALUE); - } catch (ex: any) { - throw new OptimizelyError(INVALID_BUCKETING_ID, bucketingKey, ex.message); - } -}; - export default { bucket: bucket, bucketUserIntoExperiment: bucketUserIntoExperiment, - _generateBucketValue: _generateBucketValue, }; diff --git a/lib/core/custom_attribute_condition_evaluator/index.spec.ts b/lib/core/custom_attribute_condition_evaluator/index.spec.ts new file mode 100644 index 000000000..66f8cae0d --- /dev/null +++ b/lib/core/custom_attribute_condition_evaluator/index.spec.ts @@ -0,0 +1,1411 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as customAttributeEvaluator from './'; +import { MISSING_ATTRIBUTE_VALUE, UNEXPECTED_TYPE_NULL } from 'log_message'; +import { UNKNOWN_MATCH_TYPE, UNEXPECTED_TYPE, OUT_OF_BOUNDS, UNEXPECTED_CONDITION_VALUE } from 'error_message'; +import { Condition } from '../../shared_types'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { LoggerFacade } from '../../logging/logger'; + +const browserConditionSafari = { + name: 'browser_type', + value: 'safari', + type: 'custom_attribute', +}; +const booleanCondition = { + name: 'is_firefox', + value: true, + type: 'custom_attribute', +}; +const integerCondition = { + name: 'num_users', + value: 10, + type: 'custom_attribute', +}; +const doubleCondition = { + name: 'pi_value', + value: 3.14, + type: 'custom_attribute', +}; + +const getMockUserContext: any = (attributes: any) => ({ + getAttributes: () => ({ ...(attributes || {}) }), +}); + +const setLogSpy = (logger: LoggerFacade) => { + vi.spyOn(logger, 'error'); + vi.spyOn(logger, 'debug'); + vi.spyOn(logger, 'info'); + vi.spyOn(logger, 'warn'); +}; + +describe('custom_attribute_condition_evaluator', () => { + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true when the attributes pass the audience conditions and no match type is provided', () => { + const userAttributes = { + browser_type: 'safari', + }; + + expect( + customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes)) + ).toBe(true); + }); + + it('should return false when the attributes do not pass the audience conditions and no match type is provided', () => { + const userAttributes = { + browser_type: 'firefox', + }; + + expect( + customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes)) + ).toBe(false); + }); + + it('should evaluate different typed attributes', () => { + const userAttributes = { + browser_type: 'safari', + is_firefox: true, + num_users: 10, + pi_value: 3.14, + }; + + expect( + customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes)) + ).toBe(true); + expect(customAttributeEvaluator.getEvaluator().evaluate(booleanCondition, getMockUserContext(userAttributes))).toBe( + true + ); + expect(customAttributeEvaluator.getEvaluator().evaluate(integerCondition, getMockUserContext(userAttributes))).toBe( + true + ); + expect(customAttributeEvaluator.getEvaluator().evaluate(doubleCondition, getMockUserContext(userAttributes))).toBe( + true + ); + }); + + it('should log and return null when condition has an invalid match property', () => { + const invalidMatchCondition = { match: 'weird', name: 'weird_condition', type: 'custom_attribute', value: 'hi' }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidMatchCondition, getMockUserContext({ weird_condition: 'bye' })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNKNOWN_MATCH_TYPE, JSON.stringify(invalidMatchCondition)); + }); +}); + +describe('exists match type', () => { + const existsCondition = { + match: 'exists', + name: 'input_value', + type: 'custom_attribute', + value: '', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if there is no user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(existsCondition, getMockUserContext({})); + + expect(result).toBe(false); + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); + + it('should return false if the user-provided value is undefined', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: undefined })); + + expect(result).toBe(false); + }); + + it('should return false if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: null })); + + expect(result).toBe(false); + }); + + it('should return true if the user-provided value is a string', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: 'hi' })); + + expect(result).toBe(true); + }); + + it('should return true if the user-provided value is a number', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: 10 })); + + expect(result).toBe(true); + }); + + it('should return true if the user-provided value is a boolean', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: true })); + + expect(result).toBe(true); + }); +}); + +describe('exact match type - with a string condition value', () => { + const exactStringCondition = { + match: 'exact', + name: 'favorite_constellation', + type: 'custom_attribute', + value: 'Lacerta', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(exactStringCondition, getMockUserContext({ favorite_constellation: 'Lacerta' })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(exactStringCondition, getMockUserContext({ favorite_constellation: 'The Big Dipper' })); + + expect(result).toBe(false); + }); + + it('should log and return null if condition value is of an unexpected type', () => { + const invalidExactCondition = { + match: 'exact', + name: 'favorite_constellation', + type: 'custom_attribute', + value: null, + }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidExactCondition, getMockUserContext({ favorite_constellation: 'Lacerta' })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidExactCondition)); + }); + + it('should log and return null if the user-provided value is of a different type than the condition value', () => { + const unexpectedTypeUserAttributes: Record<string, boolean> = { favorite_constellation: false }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes)); + const userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; + const userValueType = typeof userValue; + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactStringCondition), + userValueType, + exactStringCondition.name + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext({ favorite_constellation: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(exactStringCondition), + exactStringCondition.name + ); + }); + + it('should log and return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext({})); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + MISSING_ATTRIBUTE_VALUE, + JSON.stringify(exactStringCondition), + exactStringCondition.name + ); + }); + + it('should log and return null if the user-provided value is of an unexpected type', () => { + const unexpectedTypeUserAttributes: Record<string, unknown> = { favorite_constellation: [] }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes)); + const userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; + const userValueType = typeof userValue; + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactStringCondition), + userValueType, + exactStringCondition.name + ); + }); +}); + +describe('exact match type - with a number condition value', () => { + const exactNumberCondition = { + match: 'exact', + name: 'lasers_count', + type: 'custom_attribute', + value: 9000, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 9000 })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 8000 })); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is of a different type than the condition value', () => { + const unexpectedTypeUserAttributes1: Record<string, any> = { lasers_count: 'yes' }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes1)); + + expect(result).toBe(null); + + const unexpectedTypeUserAttributes2: Record<string, any> = { lasers_count: '1000' }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes2)); + + expect(result).toBe(null); + + const userValue1 = unexpectedTypeUserAttributes1[exactNumberCondition.name]; + const userValueType1 = typeof userValue1; + const userValue2 = unexpectedTypeUserAttributes2[exactNumberCondition.name]; + const userValueType2 = typeof userValue2; + + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactNumberCondition), + userValueType1, + exactNumberCondition.name + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactNumberCondition), + userValueType2, + exactNumberCondition.name + ); + }); + + it('should log and return null if the user-provided number value is out of bounds', () => { + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Infinity })); + + expect(result).toBe(null); + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Math.pow(2, 53) - 2 })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + OUT_OF_BOUNDS, + JSON.stringify(exactNumberCondition), + exactNumberCondition.name + ); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); + + it('should log and return null if the condition value is not finite', () => { + const invalidValueCondition1 = { + match: 'exact', + name: 'lasers_count', + type: 'custom_attribute', + value: Infinity, + }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition1, getMockUserContext({ lasers_count: 9000 })); + + expect(result).toBe(null); + + const invalidValueCondition2 = { + match: 'exact', + name: 'lasers_count', + type: 'custom_attribute', + value: Math.pow(2, 53) + 2, + }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition2, getMockUserContext({ lasers_count: 9000 })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition1)); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition2)); + }); +}); + +describe('exact match type - with a boolean condition value', () => { + const exactBoolCondition = { + match: 'exact', + name: 'did_register_user', + type: 'custom_attribute', + value: false, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({ did_register_user: false })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({ did_register_user: true })); + + expect(result).toBe(false); + }); + + it('should return null if the user-provided value is of a different type than the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({ did_register_user: 10 })); + + expect(result).toBe(null); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('substring match type', () => { + const mockLogger = getMockLogger(); + const substringCondition = { + match: 'substring', + name: 'headline_text', + type: 'custom_attribute', + value: 'buy now', + }; + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the condition value is a substring of the user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + substringCondition, + getMockUserContext({ + headline_text: 'Limited time, buy now!', + }) + ); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not a substring of the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + substringCondition, + getMockUserContext({ + headline_text: 'Breaking news!', + }) + ); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is not a string', () => { + const unexpectedTypeUserAttributes: Record<string, unknown> = { headline_text: 10 }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(substringCondition, getMockUserContext(unexpectedTypeUserAttributes)); + const userValue = unexpectedTypeUserAttributes[substringCondition.name]; + const userValueType = typeof userValue; + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(substringCondition), + userValueType, + substringCondition.name + ); + }); + + it('should log and return null if the condition value is not a string', () => { + const nonStringCondition = { + match: 'substring', + name: 'headline_text', + type: 'custom_attribute', + value: 10, + }; + + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(nonStringCondition, getMockUserContext({ headline_text: 'hello' })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(nonStringCondition)); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(substringCondition, getMockUserContext({ headline_text: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(substringCondition), + substringCondition.name + ); + }); + + it('should return null if there is no user-provided value', function() { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(substringCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('greater than match type', () => { + const gtCondition = { + match: 'gt', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is greater than the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: 58.4 })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not greater than the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: 20 })); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is not a number', () => { + const unexpectedTypeUserAttributes1 = { meters_travelled: 'a long way' }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext(unexpectedTypeUserAttributes1)); + + expect(result).toBeNull(); + + const unexpectedTypeUserAttributes2 = { meters_travelled: '1000' }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext(unexpectedTypeUserAttributes2)); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(gtCondition), + 'string', + gtCondition.name + ); + }); + + it('should log and return null if the user-provided number value is out of bounds', () => { + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: -Infinity })); + + expect(result).toBeNull(); + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2 })); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith(OUT_OF_BOUNDS, JSON.stringify(gtCondition), gtCondition.name); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: null })); + + expect(result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(UNEXPECTED_TYPE_NULL, JSON.stringify(gtCondition), gtCondition.name); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(gtCondition, getMockUserContext({})); + + expect(result).toBeNull(); + }); + + it('should return null if the condition value is not a finite number', () => { + const userAttributes = { meters_travelled: 58.4 }; + const invalidValueCondition: Condition = { + match: 'gt', + name: 'meters_travelled', + type: 'custom_attribute', + value: Infinity, + }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = null; + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = Math.pow(2, 53) + 2; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(3); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)); + }); +}); + +describe('less than match type', () => { + const ltCondition = { + match: 'lt', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is less than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + ltCondition, + getMockUserContext({ + meters_travelled: 10, + }) + ); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not less than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + ltCondition, + getMockUserContext({ + meters_travelled: 64.64, + }) + ); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is not a number', () => { + const unexpectedTypeUserAttributes1: Record<string, unknown> = { meters_travelled: true }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext(unexpectedTypeUserAttributes1)); + + expect(result).toBeNull(); + + const unexpectedTypeUserAttributes2: Record<string, unknown> = { meters_travelled: '48.2' }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext(unexpectedTypeUserAttributes2)); + + expect(result).toBeNull(); + + const userValue1 = unexpectedTypeUserAttributes1[ltCondition.name]; + const userValueType1 = typeof userValue1; + const userValue2 = unexpectedTypeUserAttributes2[ltCondition.name]; + const userValueType2 = typeof userValue2; + + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(ltCondition), + userValueType1, + ltCondition.name + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(ltCondition), + userValueType2, + ltCondition.name + ); + }); + + it('should log and return null if the user-provided number value is out of bounds', () => { + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext({ meters_travelled: Infinity })); + + expect(result).toBeNull(); + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2 })); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith(OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name); + expect(mockLogger.warn).toHaveBeenCalledWith(OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext({ meters_travelled: null })); + + expect(result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(UNEXPECTED_TYPE_NULL, JSON.stringify(ltCondition), ltCondition.name); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(ltCondition, getMockUserContext({})); + + expect(result).toBeNull(); + }); + + it('should return null if the condition value is not a finite number', () => { + const userAttributes = { meters_travelled: 10 }; + const invalidValueCondition: Condition = { + match: 'lt', + name: 'meters_travelled', + type: 'custom_attribute', + value: Infinity, + }; + + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = null; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = Math.pow(2, 53) + 2; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(3); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)); + }); +}); + +describe('less than or equal match type', () => { + const leCondition = { + match: 'le', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided value is greater than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + leCondition, + getMockUserContext({ + meters_travelled: 48.3, + }) + ); + + expect(result).toBe(false); + }); + + it('should return true if the user-provided value is less than or equal to the condition value', () => { + const versions = [48, 48.2]; + for (const userValue of versions) { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + leCondition, + getMockUserContext({ + meters_travelled: userValue, + }) + ); + + expect(result).toBe(true); + } + }); +}); + +describe('greater than and equal to match type', () => { + const geCondition = { + match: 'ge', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided value is less than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + geCondition, + getMockUserContext({ + meters_travelled: 48, + }) + ); + + expect(result).toBe(false); + }); + + it('should return true if the user-provided value is less than or equal to the condition value', () => { + const versions = [100, 48.2]; + versions.forEach(userValue => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + geCondition, + getMockUserContext({ + meters_travelled: userValue, + }) + ); + + expect(result).toBe(true); + }); + }); +}); + +describe('semver greater than match type', () => { + const semvergtCondition = { + match: 'semver_gt', + name: 'app_version', + type: 'custom_attribute', + value: '2.0.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided version is greater than the condition version', () => { + const versions = [['1.8.1', '1.9']]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvergtCondition = { + match: 'semver_gt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvergtCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should return false if the user-provided version is not greater than the condition version', function() { + const versions = [ + ['2.0.1', '2.0.1'], + ['2.0', '2.0.0'], + ['2.0', '2.0.1'], + ['2.0.1', '2.0.0'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvergtCondition = { + match: 'semver_gt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvergtCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should log and return null if the user-provided version is not a string', () => { + let result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvergtCondition, + getMockUserContext({ + app_version: 22, + }) + ); + + expect(result).toBe(null); + + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvergtCondition, + getMockUserContext({ + app_version: false, + }) + ); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvergtCondition), + 'number', + 'app_version' + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvergtCondition), + 'boolean', + 'app_version' + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvergtCondition, getMockUserContext({ app_version: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(semvergtCondition), + 'app_version' + ); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvergtCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('semver less than match type', () => { + const semverltCondition = { + match: 'semver_lt', + name: 'app_version', + type: 'custom_attribute', + value: '2.0.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided version is greater than the condition version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['1.9', '2.0.0'], + ['2.0.0', '2.0.0'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemverltCondition = { + match: 'semver_lt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemverltCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should return true if the user-provided version is less than the condition version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['2.0.0', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemverltCondition = { + match: 'semver_lt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemverltCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should log and return null if the user-provided version is not a string', () => { + let result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semverltCondition, + getMockUserContext({ + app_version: 22, + }) + ); + + expect(result).toBe(null); + + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semverltCondition, + getMockUserContext({ + app_version: false, + }) + ); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semverltCondition), + 'number', + 'app_version' + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semverltCondition), + 'boolean', + 'app_version' + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semverltCondition, getMockUserContext({ app_version: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(semverltCondition), + 'app_version' + ); + }); + + it('should return null if there is no user-provided value', function() { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semverltCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); +describe('semver equal to match type', () => { + const semvereqCondition = { + match: 'semver_eq', + name: 'app_version', + type: 'custom_attribute', + value: '2.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided version is greater than the condition version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['2.0.1', '2.0.0'], + ['1.9.1', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_eq', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should return true if the user-provided version is equal to the condition version', () => { + const versions = [ + ['2.0.1', '2.0.1'], + ['1.9', '1.9.1'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_eq', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should log and return null if the user-provided version is not a string', () => { + let result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvereqCondition, + getMockUserContext({ + app_version: 22, + }) + ); + + expect(result).toBe(null); + + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvereqCondition, + getMockUserContext({ + app_version: false, + }) + ); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvereqCondition), + 'number', + 'app_version' + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvereqCondition), + 'boolean', + 'app_version' + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvereqCondition, getMockUserContext({ app_version: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(semvereqCondition), + 'app_version' + ); + }); + + it('should return null if there is no user-provided value', function() { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvereqCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('semver less than or equal to match type', () => { + const semverleCondition = { + match: 'semver_le', + name: 'app_version', + type: 'custom_attribute', + value: '2.0.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided version is greater than the condition version', () => { + const versions = [['2.0.0', '2.0.1']]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_le', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should return true if the user-provided version is less than or equal to the condition version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['2.0.1', '2.0.1'], + ['1.9', '1.9.1'], + ['1.9.1', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_le', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should return true if the user-provided version is equal to the condition version', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semverleCondition, + getMockUserContext({ + app_version: '2.0', + }) + ); + + expect(result).toBe(true); + }); +}); + +describe('semver greater than or equal to match type', () => { + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided version is greater than or equal to the condition version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['2.0.1', '2.0.1'], + ['1.9', '1.9.1'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_ge', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should return false if the user-provided version is less than the condition version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['1.9.1', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_ge', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); +}); diff --git a/lib/core/decision/index.spec.ts b/lib/core/decision/index.spec.ts new file mode 100644 index 000000000..ea98fba39 --- /dev/null +++ b/lib/core/decision/index.spec.ts @@ -0,0 +1,128 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { rolloutDecisionObj, featureTestDecisionObj } from '../../tests/test_data'; +import * as decision from './'; + +describe('getExperimentKey method', () => { + it('should return empty string when experiment is null', () => { + const experimentKey = decision.getExperimentKey(rolloutDecisionObj); + + expect(experimentKey).toEqual(''); + }); + + it('should return empty string when experiment is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const experimentKey = decision.getExperimentKey({}); + + expect(experimentKey).toEqual(''); + }); + + it('should return experiment key when experiment is defined', () => { + const experimentKey = decision.getExperimentKey(featureTestDecisionObj); + + expect(experimentKey).toEqual('testing_my_feature'); + }); +}); + +describe('getExperimentId method', () => { + it('should return null when experiment is null', () => { + const experimentId = decision.getExperimentId(rolloutDecisionObj); + + expect(experimentId).toEqual(null); + }); + + it('should return null when experiment is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const experimentId = decision.getExperimentId({}); + + expect(experimentId).toEqual(null); + }); + + it('should return experiment id when experiment is defined', () => { + const experimentId = decision.getExperimentId(featureTestDecisionObj); + + expect(experimentId).toEqual('594098'); + }); + + describe('getVariationKey method', ()=> { + it('should return empty string when variation is null', () => { + const variationKey = decision.getVariationKey(rolloutDecisionObj); + + expect(variationKey).toEqual(''); + }); + + it('should return empty string when variation is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const variationKey = decision.getVariationKey({}); + + expect(variationKey).toEqual(''); + }); + + it('should return variation key when variation is defined', () => { + const variationKey = decision.getVariationKey(featureTestDecisionObj); + + expect(variationKey).toEqual('variation'); + }); + }); + + describe('getVariationId method', () => { + it('should return null when variation is null', () => { + const variationId = decision.getVariationId(rolloutDecisionObj); + + expect(variationId).toEqual(null); + }); + + it('should return null when variation is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const variationId = decision.getVariationId({}); + + expect(variationId).toEqual(null); + }); + + it('should return variation id when variation is defined', () => { + const variationId = decision.getVariationId(featureTestDecisionObj); + + expect(variationId).toEqual('594096'); + }); + }); + + describe('getFeatureEnabledFromVariation method', () => { + it('should return false when variation is null', () => { + const featureEnabled = decision.getFeatureEnabledFromVariation(rolloutDecisionObj); + + expect(featureEnabled).toEqual(false); + }); + + it('should return false when variation is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const featureEnabled = decision.getFeatureEnabledFromVariation({}); + + expect(featureEnabled).toEqual(false); + }); + + it('should return featureEnabled boolean when variation is defined', () => { + const featureEnabled = decision.getFeatureEnabledFromVariation(featureTestDecisionObj); + + expect(featureEnabled).toEqual(true); + }); + }); +}); diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 431b95efa..b723d118b 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -20,6 +20,7 @@ import { sprintf } from '../../utils/fns'; import { createDecisionService } from './'; import * as bucketer from '../bucketer'; +import * as bucketValueGenerator from '../bucketer/bucket_value_generator'; import { LOG_LEVEL, DECISION_SOURCES, @@ -2227,7 +2228,7 @@ describe('lib/core/decision_service', function() { var generateBucketValueStub; beforeEach(function() { feature = configObj.featureKeyMap.test_feature_in_exclusion_group; - generateBucketValueStub = sandbox.stub(bucketer, '_generateBucketValue'); + generateBucketValueStub = sandbox.stub(bucketValueGenerator, 'generateBucketValue'); }); it('returns a decision with a variation in mutex group bucket less than 2500', function() { @@ -2407,7 +2408,7 @@ describe('lib/core/decision_service', function() { var generateBucketValueStub; beforeEach(function() { feature = configObj.featureKeyMap.test_feature_in_multiple_experiments; - generateBucketValueStub = sandbox.stub(bucketer, '_generateBucketValue'); + generateBucketValueStub = sandbox.stub(bucketValueGenerator, 'generateBucketValue'); }); it('returns a decision with a variation in mutex group bucket less than 2500', function() { diff --git a/lib/project_config/optimizely_config.spec.ts b/lib/project_config/optimizely_config.spec.ts index 3e7288a8e..ab8d3ab5d 100644 --- a/lib/project_config/optimizely_config.spec.ts +++ b/lib/project_config/optimizely_config.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index 2ab002bca..a955b725a 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/tests/test_data.ts b/lib/tests/test_data.ts index d792188fa..990096f7b 100644 --- a/lib/tests/test_data.ts +++ b/lib/tests/test_data.ts @@ -3573,12 +3573,14 @@ export var featureTestDecisionObj = { id: '594096', featureEnabled: true, variables: [], + variablesMap: {}, }, { key: 'control', id: '594097', featureEnabled: true, variables: [], + variablesMap: {} }, ], status: 'Running', @@ -3590,20 +3592,24 @@ export var featureTestDecisionObj = { id: '594096', featureEnabled: true, variables: [], + variablesMap: {} }, control: { key: 'control', id: '594097', featureEnabled: true, variables: [], + variablesMap: {} }, }, + audienceConditions: [] }, variation: { key: 'variation', id: '594096', featureEnabled: true, variables: [], + variablesMap: {} }, decisionSource: 'feature-test', }; From fd16fb54b0bb47ff0f6a0694996d753943e11248 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 19 Feb 2025 23:01:00 +0600 Subject: [PATCH 127/200] [FSSDK-10992] export clientEngine from entrypoint (#1006) This will help debug issues where incorrect entrypoint is used by a client --- lib/entrypoint.test-d.ts | 3 +++ lib/index.browser.ts | 3 +++ lib/index.node.ts | 2 ++ lib/index.react_native.ts | 2 ++ 4 files changed, 10 insertions(+) diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts index 393a7cdb8..a9a782522 100644 --- a/lib/entrypoint.test-d.ts +++ b/lib/entrypoint.test-d.ts @@ -94,6 +94,9 @@ export type Entrypoint = { // decide options OptimizelyDecideOption: typeof OptimizelyDecideOption; + + // client engine + clientEngine: string; } diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 8be972be3..b8c31659d 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -17,6 +17,7 @@ import { Config, Client } from './shared_types'; import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import { getOptimizelyInstance } from './client_factory'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums'; /** * Creates an instance of the Optimizely class @@ -55,3 +56,5 @@ export { createVuidManager } from './vuid/vuid_manager_factory.browser'; export * from './common_exports'; export * from './export_types'; + +export const clientEngine: string = JAVASCRIPT_CLIENT_ENGINE; diff --git a/lib/index.node.ts b/lib/index.node.ts index e39501e83..cb1802af8 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -48,3 +48,5 @@ export { createVuidManager } from './vuid/vuid_manager_factory.node'; export * from './common_exports'; export * from './export_types'; + +export const clientEngine: string = NODE_CLIENT_ENGINE; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 4cf20fccd..48a8ee35c 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -51,3 +51,5 @@ export { createVuidManager } from './vuid/vuid_manager_factory.react_native'; export * from './common_exports'; export * from './export_types'; + +export const clientEngine: string = REACT_NATIVE_JS_CLIENT_ENGINE; From cf595be7630a548b2cbc6a79636f62b3d25fd8d6 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 21 Feb 2025 00:11:44 +0600 Subject: [PATCH 128/200] [FSSDK-10992] refactor build and export map (#1007) --- package.json | 78 ++++++++++++++++++------------------------------ rollup.config.js | 77 ++++++++++++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index e7a8fbd77..7df6c1b51 100644 --- a/package.json +++ b/package.json @@ -2,71 +2,51 @@ "name": "@optimizely/optimizely-sdk", "version": "5.3.4", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", - "module": "dist/optimizely.browser.es.js", - "main": "dist/optimizely.node.min.js", - "browser": "dist/optimizely.browser.min.js", - "react-native": "dist/optimizely.react_native.min.js", - "typings": "dist/index.browser.d.ts", + "main": "./dist/index.node.min.js", + "browser": "./dist/index.browser.es.min.js", + "react-native": "./dist/index.react_native.min.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "node": { - "types": "./dist/index.node.d.ts", - "default": "./dist/optimizely.node.min.js" + "import": "./dist/index.node.es.min.mjs", + "require": "./dist/index.node.min.js" }, "react-native": { - "types": "./dist/index.react_native.d.ts", - "default": "./dist/optimizely.react_native.min.js" + "import": "./dist/index.react_native.es.min.js", + "require": "./dist/index.react_native.min.js" + }, + "browser": { + "import": "./dist/index.browser.es.min.js", + "require": "./dist/index.browser.min.js" }, "default": { - "types": "./dist/index.browser.d.ts", - "require": "./dist/optimizely.browser.min.js", - "import": "./dist/optimizely.browser.es.js", - "default": "./dist/optimizely.browser.es.min.js" + "import": "./dist/index.node.es.min.mjs", + "require": "./dist/index.node.min.js" } }, "./node": { - "types": "./dist/index.node.d.ts", - "default": "./dist/optimizely.node.min.js" + "types": "./dist/index.d.ts", + "import": "./dist/index.node.es.min.mjs", + "require": "./dist/index.node.min.js" }, "./browser": { - "types": "./dist/index.browser.d.ts", - "require": "./dist/optimizely.browser.min.js", - "import": "./dist/optimizely.browser.es.js", - "default": "./dist/optimizely.browser.es.min.js" + "types": "./dist/index.d.ts", + "import": "./dist/index.browser.es.min.js", + "require": "./dist/index.browser.min.js" }, "./react_native": { - "types": "./dist/index.react_native.d.ts", - "default": "./dist/optimizely.react_native.min.js" + "types": "./dist/index.d.ts", + "default": "./dist/index.react_native.min.js", + "import": "./dist/index.react_native.es.min.js", + "require": "./dist/index.react_native.min.js" }, "./lite": { "types": "./dist/index.lite.d.ts", - "node": "./dist/optimizely.lite.min.js", - "import": "./dist/optimizely.lite.es.js", - "default": "./dist/optimizely.lite.min.js" - }, - "./dist/optimizely.lite.es": { - "types": "./dist/index.lite.d.ts", - "default": "./dist/optimizely.lite.es.js" - }, - "./dist/optimizely.lite.es.js": { - "types": "./dist/index.lite.d.ts", - "default": "./dist/optimizely.lite.es.js" - }, - "./dist/optimizely.lite.es.min": { - "types": "./dist/index.lite.d.ts", - "default": "./dist/optimizely.lite.es.min.js" - }, - "./dist/optimizely.lite.es.min.js": { - "types": "./dist/index.lite.d.ts", - "default": "./dist/optimizely.lite.es.min.js" - }, - "./dist/optimizely.lite.min": { - "types": "./dist/index.lite.d.ts", - "default": "./dist/optimizely.lite.min.js" - }, - "./dist/optimizely.lite.min.js": { - "types": "./dist/index.lite.d.ts", - "default": "./dist/optimizely.lite.min.js" + "node": "./dist/index.lite.min.js", + "import": "./dist/index.lite.es.js", + "default": "./dist/index.lite.min.js" } }, "scripts": { @@ -82,7 +62,7 @@ "test-umdbrowser": "npm run build-browser-umd && karma start karma.umd.conf.js --single-run", "test-karma-local": "karma start karma.local_chrome.bs.conf.js && npm run build-browser-umd && karma start karma.local_chrome.umd.conf.js", "prebuild": "npm run clean", - "build": "npm run genmsg && rollup -c && cp dist/index.lite.d.ts dist/optimizely.lite.es.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.es.min.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.min.d.ts", + "build": "npm run genmsg && rollup -c && cp dist/index.browser.d.ts dist/index.d.ts", "build:win": "npm run genmsg && rollup -c && type nul > dist/optimizely.lite.es.d.ts && type nul > dist/optimizely.lite.es.min.d.ts && type nul > dist/optimizely.lite.min.d.ts", "build-browser-umd": "rollup -c --config-umd", "coveralls": "nyc --reporter=lcov npm test", diff --git a/rollup.config.js b/rollup.config.js index 68d495c9c..046cdab1e 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -48,35 +48,50 @@ const typescriptPluginOptions = { } }; -const cjsBundleFor = platform => ({ - plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], - external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})), - input: `lib/index.${platform}.ts`, - output: { - exports: 'named', - format: 'cjs', - file: `dist/optimizely.${platform}.min.js`, - plugins: [terser()], - sourcemap: true, - }, -}); +const cjsBundleFor = (platform, opt = {}) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; -const esmBundleFor = platform => ({ - ...cjsBundleFor(platform), - output: [ - { - format: 'es', - file: `dist/optimizely.${platform}.es.js`, - sourcemap: true, - }, - { - format: 'es', - file: `dist/optimizely.${platform}.es.min.js`, - plugins: [terser()], + const min = minify ? '.min' : ''; + + return { + plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], + external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})), + input: `lib/index.${platform}.ts`, + output: { + exports: 'named', + format: 'cjs', + file: `dist/index.${platform}${min}${ext}`, + plugins: minify ? [terser()] : undefined, sourcemap: true, }, - ], -}); + } +}; + +const esmBundleFor = (platform, opt) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; + + const min = minify ? '.min' : ''; + + return { + ...cjsBundleFor(platform), + output: [ + { + format: 'es', + file: `dist/index.${platform}.es${min}${ext}`, + plugins: minify ? [terser()] : undefined, + sourcemap: true, + }, + ], + } +}; const umdBundle = { plugins: [ @@ -123,11 +138,13 @@ const jsonSchemaBundle = { }; const bundles = { - 'cjs-node': cjsBundleFor('node'), - 'cjs-browser': cjsBundleFor('browser'), - 'cjs-react-native': cjsBundleFor('react_native'), + 'cjs-node-min': cjsBundleFor('node'), + 'cjs-browser-min': cjsBundleFor('browser'), + 'cjs-react-native-min': cjsBundleFor('react_native'), 'cjs-lite': cjsBundleFor('lite'), - esm: esmBundleFor('browser'), + 'esm-browser-min': esmBundleFor('browser'), + 'esm-node-min': esmBundleFor('node', { ext: '.mjs' }), + 'esm-react-native-min': esmBundleFor('react_native'), 'esm-lite': esmBundleFor('lite'), 'json-schema': jsonSchemaBundle, umd: umdBundle, From 2089d96952f44bf039a30226bd04694a879554cf Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 25 Feb 2025 23:19:56 +0600 Subject: [PATCH 129/200] [FSSDK-11124] add cmab experiment properties to project config (#1009) --- lib/project_config/project_config.spec.ts | 28 +++++++++++++++++++++ lib/project_config/project_config.ts | 10 ++++++++ lib/project_config/project_config_schema.ts | 13 ++++++++++ lib/shared_types.ts | 3 +++ 4 files changed, 54 insertions(+) diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index a955b725a..bb5370ef4 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -252,6 +252,34 @@ describe('createProjectConfig - flag variations', () => { }); }); +describe('createProjectConfig - cmab experiments', () => { + it('should populate cmab field correctly', function() { + const datafile = testDatafile.getTestProjectConfig(); + datafile.experiments[0].cmab = { + attributes: ['808797688', '808797689'], + }; + + datafile.experiments[2].cmab = { + attributes: ['808797689'], + }; + + const configObj = projectConfig.createProjectConfig(datafile); + + const experiment0 = configObj.experiments[0]; + expect(experiment0.cmab).toEqual({ + attributeIds: ['808797688', '808797689'], + }); + + const experiment1 = configObj.experiments[1]; + expect(experiment1.cmab).toBeUndefined(); + + const experiment2 = configObj.experiments[2]; + expect(experiment2.cmab).toEqual({ + attributeIds: ['808797689'], + }); + }); +}); + describe('getExperimentId', () => { let testData: Record<string, any>; let configObj: ProjectConfig; diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index a41347916..756c8c058 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -114,6 +114,7 @@ const RESERVED_ATTRIBUTE_PREFIX = '$opt_'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { const datafileCopy = { ...datafile }; + datafileCopy.audiences = (datafile.audiences || []).map((audience: Audience) => { return { ...audience }; }); @@ -155,6 +156,15 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.__datafileStr = datafileStr === null ? JSON.stringify(datafileObj) : datafileStr; + /** rename cmab.attributes field from the datafile to cmab.attributeIds for each experiment */ + projectConfig.experiments.forEach(experiment => { + if (experiment.cmab) { + const attributes = (experiment.cmab as any).attributes; + delete (experiment.cmab as any).attributes; + experiment.cmab.attributeIds = attributes; + } + }); + /* * Conditions of audiences in projectConfig.typedAudiences are not * expected to be string-encoded as they are here in projectConfig.audiences. diff --git a/lib/project_config/project_config_schema.ts b/lib/project_config/project_config_schema.ts index c33f013ae..f842179dc 100644 --- a/lib/project_config/project_config_schema.ts +++ b/lib/project_config/project_config_schema.ts @@ -202,6 +202,19 @@ var schemaDefinition = { type: 'object', required: true, }, + cmab: { + type: 'object', + required: false, + properties: { + attributes: { + type: 'array', + items: { + type: 'string', + }, + required: true, + } + } + } }, }, required: true, diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 40ad29a1f..870b55ddc 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -152,6 +152,9 @@ export interface Experiment { trafficAllocation: TrafficAllocation[]; forcedVariations?: { [key: string]: string }; isRollout?: boolean; + cmab?: { + attributeIds: string[]; + }; } export enum VariableType { From 6861f65cd5d3d7839240562c0acd668e9fac9587 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 25 Feb 2025 23:27:38 +0600 Subject: [PATCH 130/200] [FSSDK-10992] add universal entrypoint (#1008) The universal entrypoint does not contain any platform specific code, so that it can be used with any platform including the edge platforms --- lib/entrypoint.universal.test-d.ts | 95 +++++++++++++++ .../event_dispatcher_factory.ts | 23 ++++ .../event_processor_factory.universal.ts | 56 +++++++++ lib/export_types.ts | 2 - lib/index.lite.ts | 1 - lib/index.universal.ts | 115 ++++++++++++++++++ .../config_manager_factory.universal.ts | 32 +++++ package.json | 9 +- rollup.config.js | 4 +- 9 files changed, 327 insertions(+), 10 deletions(-) create mode 100644 lib/entrypoint.universal.test-d.ts create mode 100644 lib/event_processor/event_dispatcher/event_dispatcher_factory.ts create mode 100644 lib/event_processor/event_processor_factory.universal.ts delete mode 100644 lib/index.lite.ts create mode 100644 lib/index.universal.ts create mode 100644 lib/project_config/config_manager_factory.universal.ts diff --git a/lib/entrypoint.universal.test-d.ts b/lib/entrypoint.universal.test-d.ts new file mode 100644 index 000000000..1b5afb060 --- /dev/null +++ b/lib/entrypoint.universal.test-d.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expectTypeOf } from 'vitest'; + +import * as universal from './index.universal'; + +type WithoutReadonly<T> = { -readonly [P in keyof T]: T[P] }; + +const universalEntrypoint: WithoutReadonly<typeof universal> = universal; + +import { + Config, + Client, + StaticConfigManagerConfig, + OpaqueConfigManager, + EventDispatcher, + OpaqueEventProcessor, + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, + ErrorHandler, + OpaqueErrorNotifier, +} from './export_types'; + +import { UniversalPollingConfigManagerConfig } from './project_config/config_manager_factory.universal'; +import { RequestHandler } from './utils/http_request_handler/http'; +import { UniversalBatchEventProcessorOptions } from './event_processor/event_processor_factory.universal'; +import { + DECISION_SOURCES, + DECISION_NOTIFICATION_TYPES, + NOTIFICATION_TYPES, +} from './utils/enums'; + +import { LogLevel } from './logging/logger'; + +import { OptimizelyDecideOption } from './shared_types'; + +export type UniversalEntrypoint = { + // client factory + createInstance: (config: Config) => Client | null; + + // config manager related exports + createStaticProjectConfigManager: (config: StaticConfigManagerConfig) => OpaqueConfigManager; + createPollingProjectConfigManager: (config: UniversalPollingConfigManagerConfig) => OpaqueConfigManager; + + // event processor related exports + createEventDispatcher: (requestHandler: RequestHandler) => EventDispatcher; + createForwardingEventProcessor: (eventDispatcher: EventDispatcher) => OpaqueEventProcessor; + createBatchEventProcessor: (options: UniversalBatchEventProcessorOptions) => OpaqueEventProcessor; + + // TODO: odp manager related exports + // createOdpManager: (options: OdpManagerOptions) => OpaqueOdpManager; + + // TODO: vuid manager related exports + // createVuidManager: (options: VuidManagerOptions) => OpaqueVuidManager; + + // logger related exports + LogLevel: typeof LogLevel; + DebugLog: OpaqueLevelPreset, + InfoLog: OpaqueLevelPreset, + WarnLog: OpaqueLevelPreset, + ErrorLog: OpaqueLevelPreset, + createLogger: (config: LoggerConfig) => OpaqueLogger; + + // error related exports + createErrorNotifier: (errorHandler: ErrorHandler) => OpaqueErrorNotifier; + + // enums + DECISION_SOURCES: typeof DECISION_SOURCES; + DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; + NOTIFICATION_TYPES: typeof NOTIFICATION_TYPES; + + // decide options + OptimizelyDecideOption: typeof OptimizelyDecideOption; + + // client engine + clientEngine: string; +} + + +expectTypeOf(universalEntrypoint).toEqualTypeOf<UniversalEntrypoint>(); diff --git a/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts b/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts new file mode 100644 index 000000000..035fb7e49 --- /dev/null +++ b/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from '../../utils/http_request_handler/http'; +import { DefaultEventDispatcher } from './default_dispatcher'; +import { EventDispatcher } from './event_dispatcher'; + +export const createEventDispatcher = (requestHander: RequestHandler): EventDispatcher => { + return new DefaultEventDispatcher(requestHander); +} diff --git a/lib/event_processor/event_processor_factory.universal.ts b/lib/event_processor/event_processor_factory.universal.ts new file mode 100644 index 000000000..40ef4a93d --- /dev/null +++ b/lib/event_processor/event_processor_factory.universal.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getForwardingEventProcessor } from './forwarding_event_processor'; +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; + +import { + getOpaqueBatchEventProcessor, + BatchEventProcessorOptions, + OpaqueEventProcessor, + wrapEventProcessor, + getPrefixEventStore, +} from './event_processor_factory'; + +import { FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); +}; + +export type UniversalBatchEventProcessorOptions = Omit<BatchEventProcessorOptions, 'eventDispatcher'> & { + eventDispatcher: EventDispatcher; +} + +export const createBatchEventProcessor = ( + options: UniversalBatchEventProcessorOptions +): OpaqueEventProcessor => { + const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : undefined; + + return getOpaqueBatchEventProcessor({ + eventDispatcher: options.eventDispatcher, + closingEventDispatcher: options.closingEventDispatcher, + flushInterval: options.flushInterval, + batchSize: options.batchSize, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + eventStore: eventStore, + }); +}; diff --git a/lib/export_types.ts b/lib/export_types.ts index be8b77254..fba5cde09 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -67,8 +67,6 @@ export type { NotificationPayload, } from './notification_center/type'; -export type { OptimizelyDecideOption } from './shared_types'; - export type { UserAttributeValue, UserAttributes, diff --git a/lib/index.lite.ts b/lib/index.lite.ts deleted file mode 100644 index ace83107d..000000000 --- a/lib/index.lite.ts +++ /dev/null @@ -1 +0,0 @@ -const msg = 'not used'; \ No newline at end of file diff --git a/lib/index.universal.ts b/lib/index.universal.ts new file mode 100644 index 000000000..5df959975 --- /dev/null +++ b/lib/index.universal.ts @@ -0,0 +1,115 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Client, Config } from './shared_types'; +import { getOptimizelyInstance } from './client_factory'; +import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums'; + +/** + * Creates an instance of the Optimizely class + * @param {Config} config + * @return {Client|null} the Optimizely client object + * null on error + */ +export const createInstance = function(config: Config): Client | null { + return getOptimizelyInstance(config); +}; + +export { createEventDispatcher } from './event_processor/event_dispatcher/event_dispatcher_factory'; + +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.universal'; + +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.universal'; + +// TODO: decide on universal odp manager factory interface +// export { createOdpManager } from './odp/odp_manager_factory.node'; +// export { createVuidManager } from './vuid/vuid_manager_factory.node'; + +export * from './common_exports'; + +export const clientEngine: string = JAVASCRIPT_CLIENT_ENGINE; + +// type exports +export type { RequestHandler } from './utils/http_request_handler/http'; + +// config manager related types +export type { + StaticConfigManagerConfig, + OpaqueConfigManager, +} from './project_config/config_manager_factory'; + +export type { UniversalPollingConfigManagerConfig } from './project_config/config_manager_factory.universal'; + +// event processor related types +export type { + LogEvent, + EventDispatcherResponse, + EventDispatcher, +} from './event_processor/event_dispatcher/event_dispatcher'; + +export type { UniversalBatchEventProcessorOptions } from './event_processor/event_processor_factory.universal'; + +export type { + OpaqueEventProcessor, +} from './event_processor/event_processor_factory'; + +// Logger related types +export type { + LogHandler, +} from './logging/logger'; + +export type { + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, +} from './logging/logger_factory'; + +// Error related types +export type { ErrorHandler } from './error/error_handler'; +export type { OpaqueErrorNotifier } from './error/error_notifier_factory'; + +export type { Cache } from './utils/cache/cache'; + +export type { + NotificationType, + NotificationPayload, +} from './notification_center/type'; + +export type { + UserAttributeValue, + UserAttributes, + OptimizelyConfig, + FeatureVariableValue, + OptimizelyVariable, + OptimizelyVariation, + OptimizelyExperiment, + OptimizelyFeature, + OptimizelyDecisionContext, + OptimizelyForcedDecision, + EventTags, + Event, + DatafileOptions, + UserProfileService, + UserProfile, + ListenerPayload, + OptimizelyDecision, + OptimizelyUserContext, + Config, + Client, + ActivateListenerPayload, + TrackListenerPayload, + NotificationCenter, + OptimizelySegmentOption, +} from './shared_types'; diff --git a/lib/project_config/config_manager_factory.universal.ts b/lib/project_config/config_manager_factory.universal.ts new file mode 100644 index 000000000..bcbd4a310 --- /dev/null +++ b/lib/project_config/config_manager_factory.universal.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { NodeRequestHandler } from "../utils/http_request_handler/request_handler.node"; +import { ProjectConfigManager } from "./project_config_manager"; +import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './constant'; +import { RequestHandler } from "../utils/http_request_handler/http"; + +export type UniversalPollingConfigManagerConfig = PollingConfigManagerConfig & { + requestHandler: RequestHandler; +} + +export const createPollingProjectConfigManager = (config: UniversalPollingConfigManagerConfig): OpaqueConfigManager => { + const defaultConfig = { + autoUpdate: true, + }; + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/package.json b/package.json index 7df6c1b51..525302c6a 100644 --- a/package.json +++ b/package.json @@ -42,11 +42,10 @@ "import": "./dist/index.react_native.es.min.js", "require": "./dist/index.react_native.min.js" }, - "./lite": { - "types": "./dist/index.lite.d.ts", - "node": "./dist/index.lite.min.js", - "import": "./dist/index.lite.es.js", - "default": "./dist/index.lite.min.js" + "./universal": { + "types": "./dist/index.universal.d.ts", + "import": "./dist/index.universal.es.min.js", + "require": "./dist/index.universal.min.js" } }, "scripts": { diff --git a/rollup.config.js b/rollup.config.js index 046cdab1e..2fc077a83 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -141,11 +141,11 @@ const bundles = { 'cjs-node-min': cjsBundleFor('node'), 'cjs-browser-min': cjsBundleFor('browser'), 'cjs-react-native-min': cjsBundleFor('react_native'), - 'cjs-lite': cjsBundleFor('lite'), + 'cjs-universal': cjsBundleFor('universal'), 'esm-browser-min': esmBundleFor('browser'), 'esm-node-min': esmBundleFor('node', { ext: '.mjs' }), 'esm-react-native-min': esmBundleFor('react_native'), - 'esm-lite': esmBundleFor('lite'), + 'esm-universal': esmBundleFor('universal'), 'json-schema': jsonSchemaBundle, umd: umdBundle, }; From a4c27a2c36342ce40e24ebc9003b4afdf406577b Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 6 Mar 2025 00:30:38 +0600 Subject: [PATCH 131/200] [FSSDK-11125] implement CMAB client (#1010) --- .../decision_service/cmab/cmab_client.spec.ts | 357 ++++++++++++++++++ lib/core/decision_service/cmab/cmab_client.ts | 116 ++++++ lib/message/error_message.ts | 2 + 3 files changed, 475 insertions(+) create mode 100644 lib/core/decision_service/cmab/cmab_client.spec.ts create mode 100644 lib/core/decision_service/cmab/cmab_client.ts diff --git a/lib/core/decision_service/cmab/cmab_client.spec.ts b/lib/core/decision_service/cmab/cmab_client.spec.ts new file mode 100644 index 000000000..04c7246ca --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_client.spec.ts @@ -0,0 +1,357 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi, Mocked, Mock, MockInstance, beforeEach, afterEach } from 'vitest'; + +import { DefaultCmabClient } from './cmab_client'; +import { getMockAbortableRequest, getMockRequestHandler } from '../../../tests/mock/mock_request_handler'; +import { RequestHandler } from '../../../utils/http_request_handler/http'; +import { advanceTimersByTime, exhaustMicrotasks } from '../../../tests/testUtils'; +import { OptimizelyError } from '../../../error/optimizly_error'; + +const mockSuccessResponse = (variation: string) => Promise.resolve({ + statusCode: 200, + body: JSON.stringify({ + predictions: [ + { + variation_id: variation, + }, + ], + }), + headers: {} +}); + +const mockErrorResponse = (statusCode: number) => Promise.resolve({ + statusCode, + body: '', + headers: {}, +}); + +const assertRequest = ( + call: number, + mockRequestHandler: MockInstance<RequestHandler['makeRequest']>, + ruleId: string, + userId: string, + attributes: Record<string, any>, + cmabUuid: string, +) => { + const [requestUrl, headers, method, data] = mockRequestHandler.mock.calls[call]; + expect(requestUrl).toBe(`https://prediction.cmab.optimizely.com/predict/${ruleId}`); + expect(method).toBe('POST'); + expect(headers).toEqual({ + 'Content-Type': 'application/json', + }); + + const parsedData = JSON.parse(data!); + expect(parsedData.instances).toEqual([ + { + visitorId: userId, + experimentId: ruleId, + attributes: Object.keys(attributes).map((key) => ({ + id: key, + value: attributes[key], + type: 'custom_attribute', + })), + cmabUUID: cmabUuid, + } + ]); +}; + +describe('DefaultCmabClient', () => { + it('should fetch variation using correct parameters', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockSuccessResponse('var123'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + + expect(variation).toBe('var123'); + assertRequest(0, mockMakeRequest, ruleId, userId, attributes, cmabUuid); + }); + + it('should retry fetch if retryConfig is provided', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValueOnce(getMockAbortableRequest(Promise.reject('error'))) + .mockReturnValueOnce(getMockAbortableRequest(mockErrorResponse(500))) + .mockReturnValueOnce(getMockAbortableRequest(mockSuccessResponse('var123'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + + expect(variation).toBe('var123'); + expect(mockMakeRequest.mock.calls.length).toBe(3); + for(let i = 0; i < 3; i++) { + assertRequest(i, mockMakeRequest, ruleId, userId, attributes, cmabUuid); + } + }); + + it('should use backoff provider if provided', async () => { + vi.useFakeTimers(); + + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValueOnce(getMockAbortableRequest(Promise.reject('error'))) + .mockReturnValueOnce(getMockAbortableRequest(mockErrorResponse(500))) + .mockReturnValueOnce(getMockAbortableRequest(mockErrorResponse(500))) + .mockReturnValueOnce(getMockAbortableRequest(mockSuccessResponse('var123'))); + + const backoffProvider = () => { + let call = 0; + const values = [100, 200, 300]; + return { + reset: () => {}, + backoff: () => { + return values[call++]; + }, + }; + } + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + backoffProvider, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + const fetchPromise = cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(1); + + // first backoff is 100ms, should not retry yet + await advanceTimersByTime(90); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(1); + + // first backoff is 100ms, should retry now + await advanceTimersByTime(10); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(2); + + // second backoff is 200ms, should not retry 2nd time yet + await advanceTimersByTime(150); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(2); + + // second backoff is 200ms, should retry 2nd time now + await advanceTimersByTime(50); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(3); + + // third backoff is 300ms, should not retry 3rd time yet + await advanceTimersByTime(280); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(3); + + // third backoff is 300ms, should retry 3rd time now + await advanceTimersByTime(20); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(4); + + const variation = await fetchPromise; + + expect(variation).toBe('var123'); + expect(mockMakeRequest.mock.calls.length).toBe(4); + for(let i = 0; i < 4; i++) { + assertRequest(i, mockMakeRequest, ruleId, userId, attributes, cmabUuid); + } + vi.useRealTimers(); + }); + + it('should reject the promise after retries are exhausted', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(Promise.reject('error'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow(); + expect(mockMakeRequest.mock.calls.length).toBe(6); + }); + + it('should reject the promise after retries are exhausted with error status', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockErrorResponse(500))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow(); + expect(mockMakeRequest.mock.calls.length).toBe(6); + }); + + it('should not retry if retryConfig is not provided', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValueOnce(getMockAbortableRequest(Promise.reject('error'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow(); + expect(mockMakeRequest.mock.calls.length).toBe(1); + }); + + it('should reject the promise if response status code is not 200', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockErrorResponse(500))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toMatchObject( + new OptimizelyError('CMAB_FETCH_FAILED', 500), + ); + }); + + it('should reject the promise if api response is not valid', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(Promise.resolve({ + statusCode: 200, + body: JSON.stringify({ + predictions: [], + }), + headers: {}, + }))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toMatchObject( + new OptimizelyError('INVALID_CMAB_RESPONSE'), + ); + }); + + it('should reject the promise if requestHandler.makeRequest rejects', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(Promise.reject('error'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow('error'); + }); +}); diff --git a/lib/core/decision_service/cmab/cmab_client.ts b/lib/core/decision_service/cmab/cmab_client.ts new file mode 100644 index 000000000..efe3a72ed --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_client.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OptimizelyError } from "../../../error/optimizly_error"; +import { CMAB_FETCH_FAILED, INVALID_CMAB_FETCH_RESPONSE } from "../../../message/error_message"; +import { UserAttributes } from "../../../shared_types"; +import { runWithRetry } from "../../../utils/executor/backoff_retry_runner"; +import { sprintf } from "../../../utils/fns"; +import { RequestHandler } from "../../../utils/http_request_handler/http"; +import { isSuccessStatusCode } from "../../../utils/http_request_handler/http_util"; +import { BackoffController } from "../../../utils/repeater/repeater"; +import { Producer } from "../../../utils/type"; + +export interface CmabClient { + fetchDecision( + ruleId: string, + userId: string, + attributes: UserAttributes, + cmabUuid: string, + ): Promise<string> +} + +const CMAB_PREDICTION_ENDPOINT = 'https://prediction.cmab.optimizely.com/predict/%s'; + +export type RetryConfig = { + maxRetries: number, + backoffProvider?: Producer<BackoffController>; +} + +export type CmabClientConfig = { + requestHandler: RequestHandler, + retryConfig?: RetryConfig; +} + +export class DefaultCmabClient implements CmabClient { + private requestHandler: RequestHandler; + private retryConfig?: RetryConfig; + + constructor(config: CmabClientConfig) { + this.requestHandler = config.requestHandler; + this.retryConfig = config.retryConfig; + } + + async fetchDecision( + ruleId: string, + userId: string, + attributes: UserAttributes, + cmabUuid: string, + ): Promise<string> { + const url = sprintf(CMAB_PREDICTION_ENDPOINT, ruleId); + + const cmabAttributes = Object.keys(attributes).map((key) => ({ + id: key, + value: attributes[key], + type: 'custom_attribute', + })); + + const body = { + instances: [ + { + visitorId: userId, + experimentId: ruleId, + attributes: cmabAttributes, + cmabUUID: cmabUuid, + } + ] + } + + const variation = await (this.retryConfig ? + runWithRetry( + () => this.doFetch(url, JSON.stringify(body)), + this.retryConfig.backoffProvider?.(), + this.retryConfig.maxRetries, + ).result : this.doFetch(url, JSON.stringify(body)) + ); + + return variation; + } + + private async doFetch(url: string, data: string): Promise<string> { + const response = await this.requestHandler.makeRequest( + url, + { 'Content-Type': 'application/json' }, + 'POST', + data, + ).responsePromise; + + if (!isSuccessStatusCode(response.statusCode)) { + return Promise.reject(new OptimizelyError(CMAB_FETCH_FAILED, response.statusCode)); + } + + const body = JSON.parse(response.body); + if (!this.validateResponse(body)) { + return Promise.reject(new OptimizelyError(INVALID_CMAB_FETCH_RESPONSE)); + } + + return String(body.predictions[0].variation_id); + } + + private validateResponse(body: any): boolean { + return body.predictions && body.predictions.length > 0 && body.predictions[0].variation_id; + } +} diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index 66bf469f7..e6a2260a3 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -106,5 +106,7 @@ export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it co export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to page unload event: "%s"'; export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; +export const CMAB_FETCH_FAILED = 'CMAB decision fetch failed with status: %s'; +export const INVALID_CMAB_FETCH_RESPONSE = 'Invalid CMAB fetch response'; export const messages: string[] = []; From 69e3f6f3d4d8e2dbfec7f971a206d3e9a414cca5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 7 Mar 2025 02:06:15 +0600 Subject: [PATCH 132/200] [FSSDK-11127] implement cmab service (#1011) --- .../cmab/cmab_service.spec.ts | 420 ++++++++++++++++++ .../decision_service/cmab/cmab_service.ts | 156 +++++++ lib/project_config/project_config.ts | 10 +- lib/shared_types.ts | 3 + 4 files changed, 588 insertions(+), 1 deletion(-) create mode 100644 lib/core/decision_service/cmab/cmab_service.spec.ts create mode 100644 lib/core/decision_service/cmab/cmab_service.ts diff --git a/lib/core/decision_service/cmab/cmab_service.spec.ts b/lib/core/decision_service/cmab/cmab_service.spec.ts new file mode 100644 index 000000000..2e571932d --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_service.spec.ts @@ -0,0 +1,420 @@ +import { describe, it, expect, vi, Mocked, Mock, MockInstance, beforeEach, afterEach } from 'vitest'; + +import { DefaultCmabService } from './cmab_service'; +import { getMockSyncCache } from '../../../tests/mock/mock_cache'; +import { ProjectConfig } from '../../../project_config/project_config'; +import { OptimizelyDecideOption, UserAttributes } from '../../../shared_types'; +import OptimizelyUserContext from '../../../optimizely_user_context'; +import { validate as uuidValidate } from 'uuid'; + +const mockProjectConfig = (): ProjectConfig => ({ + experimentIdMap: { + '1234': { + id: '1234', + key: 'cmab_1', + cmab: { + attributeIds: ['66', '77', '88'], + } + }, + '5678': { + id: '5678', + key: 'cmab_2', + cmab: { + attributeIds: ['66', '99'], + } + }, + }, + attributeIdMap: { + '66': { + id: '66', + key: 'country', + }, + '77': { + id: '77', + key: 'age', + }, + '88': { + id: '88', + key: 'language', + }, + '99': { + id: '99', + key: 'gender', + }, + } +} as any); + +const mockUserContext = (userId: string, attributes: UserAttributes): OptimizelyUserContext => new OptimizelyUserContext({ + userId, + attributes, +} as any); + +describe('DefaultCmabService', () => { + it('should fetch and return the variation from cmabClient using correct parameters', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValue('123'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + gender: 'male', + }); + + const ruleId = '1234'; + const variation = await cmabService.getDecision(projectConfig, userContext, ruleId, []); + + expect(variation.variationId).toEqual('123'); + expect(uuidValidate(variation.cmabUuid)).toBe(true); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledOnce(); + const [ruleIdArg, userIdArg, attributesArg, cmabUuidArg] = mockCmabClient.fetchDecision.mock.calls[0]; + expect(ruleIdArg).toEqual(ruleId); + expect(userIdArg).toEqual(userContext.getUserId()); + expect(attributesArg).toEqual({ + country: 'US', + age: '25', + }); + }); + + it('should filter attributes based on experiment cmab attributeIds before fetching variation', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValue('123'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + await cmabService.getDecision(projectConfig, userContext, '1234', []); + await cmabService.getDecision(projectConfig, userContext, '5678', []); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + expect(mockCmabClient.fetchDecision.mock.calls[0][2]).toEqual({ + country: 'US', + age: '25', + language: 'en', + }); + expect(mockCmabClient.fetchDecision.mock.calls[1][2]).toEqual({ + country: 'US', + gender: 'male' + }); + }); + + it('should cache the variation and return the same variation if relevant attributes have not changed', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext11 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', []); + + const userContext12 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'female' + }); + + const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', []); + expect(variation11.variationId).toEqual('123'); + expect(variation12.variationId).toEqual('123'); + expect(variation11.cmabUuid).toEqual(variation12.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + + const userContext21 = mockUserContext('user456', { + country: 'BD', + age: '30', + }); + + const variation21 = await cmabService.getDecision(projectConfig, userContext21, '5678', []); + + const userContext22 = mockUserContext('user456', { + country: 'BD', + age: '35', + }); + + const variation22 = await cmabService.getDecision(projectConfig, userContext22, '5678', []); + expect(variation21.variationId).toEqual('456'); + expect(variation22.variationId).toEqual('456'); + expect(variation21.cmabUuid).toEqual(variation22.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should cache the variation and return the same variation if relevant attributes value have not changed but order changed', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext11 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', []); + + const userContext12 = mockUserContext('user123', { + gender: 'female', + language: 'en', + country: 'US', + age: '25', + }); + + const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', []); + expect(variation11.variationId).toEqual('123'); + expect(variation12.variationId).toEqual('123'); + expect(variation11.cmabUuid).toEqual(variation12.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + }); + + it('should not mix up the cache between different experiments', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + + const variation2 = await cmabService.getDecision(projectConfig, userContext, '5678', []); + + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + }); + + it('should not mix up the cache between different users', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + + const userContext1 = mockUserContext('user123', { + country: 'US', + age: '25', + }); + + const userContext2 = mockUserContext('user456', { + country: 'US', + age: '25', + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []); + + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should invalidate the cache and fetch a new variation if relevant attributes have changed', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext1 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []); + + const userContext2 = mockUserContext('user123', { + country: 'US', + age: '50', + language: 'en', + gender: 'male' + }); + + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should ignore the cache and fetch variation if IGNORE_CMAB_CACHE option is provided', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + + const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', [ + OptimizelyDecideOption.IGNORE_CMAB_CACHE, + ]); + + const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + + expect(variation3.variationId).toEqual('123'); + expect(variation3.cmabUuid).toEqual(variation1.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should reset the cache before fetching variation if RESET_CMAB_CACHE option is provided', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789') + .mockResolvedValueOnce('101112'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext1 = mockUserContext('user123', { + country: 'US', + age: '25' + }); + + const userContext2 = mockUserContext('user456', { + country: 'US', + age: '50' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []); + expect(variation1.variationId).toEqual('123'); + + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + expect(variation2.variationId).toEqual('456'); + + const variation3 = await cmabService.getDecision(projectConfig, userContext1, '1234', [ + OptimizelyDecideOption.RESET_CMAB_CACHE, + ]); + expect(variation3.variationId).toEqual('789'); + + const variation4 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + expect(variation4.variationId).toEqual('101112'); + }); + + it('should invalidate the cache and fetch a new variation if INVALIDATE_USER_CMAB_CACHE option is provided', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + + const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', [ + OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE, + ]); + + const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + expect(variation3.variationId).toEqual('456'); + expect(variation2.cmabUuid).toEqual(variation3.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); +}); diff --git a/lib/core/decision_service/cmab/cmab_service.ts b/lib/core/decision_service/cmab/cmab_service.ts new file mode 100644 index 000000000..2eaffd4fd --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_service.ts @@ -0,0 +1,156 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerFacade } from "../../../logging/logger"; +import OptimizelyUserContext from "../../../optimizely_user_context" +import { ProjectConfig } from "../../../project_config/project_config" +import { OptimizelyDecideOption, UserAttributes } from "../../../shared_types" +import { Cache } from "../../../utils/cache/cache"; +import { CmabClient } from "./cmab_client"; +import { v4 as uuidV4 } from 'uuid'; +import murmurhash from "murmurhash"; +import { a } from "vitest/dist/chunks/suite.CcK46U-P"; + +export type CmabDecision = { + variationId: string, + cmabUuid: string, +} + +export interface CmabService { + /** + * Get variation id for the user + * @param {OptimizelyUserContext} userContext + * @param {string} ruleId + * @param {OptimizelyDecideOption[]} options + * @return {Promise<CmabDecision>} + */ + getDecision( + projectConfig: ProjectConfig, + userContext: OptimizelyUserContext, + ruleId: string, + options: OptimizelyDecideOption[] + ): Promise<CmabDecision> +} + +export type CmabCacheValue = { + attributesHash: string, + variationId: string, + cmabUuid: string, +} + +export type CmabServiceOptions = { + logger?: LoggerFacade; + cmabCache: Cache<CmabCacheValue>; + cmabClient: CmabClient; +} + +export class DefaultCmabService implements CmabService { + private cmabCache: Cache<CmabCacheValue>; + private cmabClient: CmabClient; + private logger?: LoggerFacade; + + constructor(options: CmabServiceOptions) { + this.cmabCache = options.cmabCache; + this.cmabClient = options.cmabClient; + this.logger = options.logger; + } + + async getDecision( + projectConfig: ProjectConfig, + userContext: OptimizelyUserContext, + ruleId: string, + options: OptimizelyDecideOption[] + ): Promise<CmabDecision> { + const filteredAttributes = this.filterAttributes(projectConfig, userContext, ruleId); + + if (options.includes(OptimizelyDecideOption.IGNORE_CMAB_CACHE)) { + return this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes); + } + + if (options.includes(OptimizelyDecideOption.RESET_CMAB_CACHE)) { + this.cmabCache.clear(); + } + + const cacheKey = this.getCacheKey(userContext.getUserId(), ruleId); + + if (options.includes(OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE)) { + this.cmabCache.remove(cacheKey); + } + + const cachedValue = await this.cmabCache.get(cacheKey); + + const attributesJson = JSON.stringify(filteredAttributes, Object.keys(filteredAttributes).sort()); + const attributesHash = String(murmurhash.v3(attributesJson)); + + if (cachedValue) { + if (cachedValue.attributesHash === attributesHash) { + return { variationId: cachedValue.variationId, cmabUuid: cachedValue.cmabUuid }; + } else { + this.cmabCache.remove(cacheKey); + } + } + + const variation = await this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes); + this.cmabCache.set(cacheKey, { + attributesHash, + variationId: variation.variationId, + cmabUuid: variation.cmabUuid, + }); + + return variation; + } + + private async fetchDecision( + ruleId: string, + userId: string, + attributes: UserAttributes, + ): Promise<CmabDecision> { + const cmabUuid = uuidV4(); + const variationId = await this.cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + return { variationId, cmabUuid }; + } + + private filterAttributes( + projectConfig: ProjectConfig, + userContext: OptimizelyUserContext, + ruleId: string + ): UserAttributes { + const filteredAttributes: UserAttributes = {}; + const userAttributes = userContext.getAttributes(); + + const experiment = projectConfig.experimentIdMap[ruleId]; + if (!experiment || !experiment.cmab) { + return filteredAttributes; + } + + const cmabAttributeIds = experiment.cmab.attributeIds; + + cmabAttributeIds.forEach((aid) => { + const attribute = projectConfig.attributeIdMap[aid]; + + if (userAttributes.hasOwnProperty(attribute.key)) { + filteredAttributes[attribute.key] = userAttributes[attribute.key]; + } + }); + + return filteredAttributes; + } + + private getCacheKey(userId: string, ruleId: string): string { + const len = userId.length; + return `${len}-${userId}-${ruleId}`; + } +} diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 756c8c058..92b9c1ac5 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -88,6 +88,7 @@ export interface ProjectConfig { eventKeyMap: { [key: string]: Event }; audiences: Audience[]; attributeKeyMap: { [key: string]: { id: string } }; + attributeIdMap: { [id: string]: { key: string } }; variationIdMap: { [id: string]: OptimizelyVariation }; variationVariableUsageMap: { [id: string]: VariableUsageMap }; audiencesById: { [id: string]: Audience }; @@ -178,7 +179,14 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str ...keyBy(projectConfig.typedAudiences, 'id'), } - projectConfig.attributeKeyMap = keyBy(projectConfig.attributes, 'key'); + projectConfig.attributes = projectConfig.attributes || []; + projectConfig.attributeKeyMap = {}; + projectConfig.attributeIdMap = {}; + projectConfig.attributes.forEach(attribute => { + projectConfig.attributeKeyMap[attribute.key] = attribute; + projectConfig.attributeIdMap[attribute.id] = attribute; + }); + projectConfig.eventKeyMap = keyBy(projectConfig.events, 'key'); projectConfig.groupIdMap = keyBy(projectConfig.groups, 'id'); diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 870b55ddc..4db7c0da1 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -252,6 +252,9 @@ export enum OptimizelyDecideOption { IGNORE_USER_PROFILE_SERVICE = 'IGNORE_USER_PROFILE_SERVICE', INCLUDE_REASONS = 'INCLUDE_REASONS', EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES', + IGNORE_CMAB_CACHE = 'IGNORE_CMAB_CACHE', + RESET_CMAB_CACHE = 'RESET_CMAB_CACHE', + INVALIDATE_USER_CMAB_CACHE = 'INVALIDATE_USER_CMAB_CACHE', } /** From d68c37ab7d3f765060c468659727b2d7320b52e2 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 12 Mar 2025 20:17:41 +0600 Subject: [PATCH 133/200] remove obselete validation (#1013) * remove obselete validation * skipping test for now --------- Co-authored-by: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> --- lib/utils/config_validator/index.tests.js | 6 +++--- lib/utils/config_validator/index.ts | 13 +------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/utils/config_validator/index.tests.js b/lib/utils/config_validator/index.tests.js index 4df36a83e..2680a07da 100644 --- a/lib/utils/config_validator/index.tests.js +++ b/lib/utils/config_validator/index.tests.js @@ -30,7 +30,7 @@ import { describe('lib/utils/config_validator', function() { describe('APIs', function() { describe('validate', function() { - it('should complain if the provided error handler is invalid', function() { + it.skip('should complain if the provided error handler is invalid', function() { const ex = assert.throws(function() { configValidator.validate({ errorHandler: {}, @@ -39,7 +39,7 @@ describe('lib/utils/config_validator', function() { assert.equal(ex.baseMessage, INVALID_ERROR_HANDLER); }); - it('should complain if the provided event dispatcher is invalid', function() { + it.skip('should complain if the provided event dispatcher is invalid', function() { const ex = assert.throws(function() { configValidator.validate({ eventDispatcher: {}, @@ -48,7 +48,7 @@ describe('lib/utils/config_validator', function() { assert.equal(ex.baseMessage, INVALID_EVENT_DISPATCHER); }); - it('should complain if the provided logger is invalid', function() { + it.skip('should complain if the provided logger is invalid', function() { const ex = assert.throws(function() { configValidator.validate({ logger: {}, diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index 636791613..abd0a6967 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -43,18 +43,7 @@ const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE export const validate = function(config: unknown): boolean { if (typeof config === 'object' && config !== null) { const configObj = config as ObjectWithUnknownProperties; - const errorHandler = configObj['errorHandler']; - const eventDispatcher = configObj['eventDispatcher']; - const logger = configObj['logger']; - if (errorHandler && typeof (errorHandler as ObjectWithUnknownProperties)['handleError'] !== 'function') { - throw new OptimizelyError(INVALID_ERROR_HANDLER); - } - if (eventDispatcher && typeof (eventDispatcher as ObjectWithUnknownProperties)['dispatchEvent'] !== 'function') { - throw new OptimizelyError(INVALID_EVENT_DISPATCHER); - } - if (logger && typeof (logger as ObjectWithUnknownProperties)['info'] !== 'function') { - throw new OptimizelyError(INVALID_LOGGER); - } + // TODO: add validation return true; } throw new OptimizelyError(INVALID_CONFIG); From c6d717e2512b1440a27d1e57df2c4ed81a16e56f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 13 Mar 2025 20:08:43 +0600 Subject: [PATCH 134/200] fix log not being printed issue (#1015) --- lib/error/optimizly_error.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/error/optimizly_error.ts b/lib/error/optimizly_error.ts index 76e8f7734..658008bea 100644 --- a/lib/error/optimizly_error.ts +++ b/lib/error/optimizly_error.ts @@ -25,6 +25,10 @@ export class OptimizelyError extends Error { this.name = 'OptimizelyError'; this.baseMessage = baseMessage; this.params = params; + + // this is needed cause instanceof doesn't work for + // custom Errors when TS is compiled to es5 + Object.setPrototypeOf(this, OptimizelyError.prototype); } getMessage(resolver?: MessageResolver): string { From bc0f7ce0587b360a5106f8987b6c44178d9b3388 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 13 Mar 2025 23:29:26 +0600 Subject: [PATCH 135/200] [FSSDK-11236] refactor decision service tests (#1014) --- lib/core/decision_service/index.spec.ts | 1740 ++++++++++++++++++++++ lib/core/decision_service/index.tests.js | 720 +-------- lib/core/decision_service/index.ts | 40 +- lib/optimizely/index.tests.js | 2 +- lib/optimizely/index.ts | 7 +- lib/tests/decision_test_datafile.ts | 468 ++++++ 6 files changed, 2255 insertions(+), 722 deletions(-) create mode 100644 lib/core/decision_service/index.spec.ts create mode 100644 lib/tests/decision_test_datafile.ts diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts new file mode 100644 index 000000000..cbfbaf7be --- /dev/null +++ b/lib/core/decision_service/index.spec.ts @@ -0,0 +1,1740 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi, MockInstance, beforeEach } from 'vitest'; +import { DecisionService } from '.'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import OptimizelyUserContext from '../../optimizely_user_context'; +import { bucket } from '../bucketer'; +import { getTestProjectConfig, getTestProjectConfigWithFeatures } from '../../tests/test_data'; +import { createProjectConfig, ProjectConfig } from '../../project_config/project_config'; +import { BucketerParams, Experiment, UserProfile } from '../../shared_types'; +import { CONTROL_ATTRIBUTES, DECISION_SOURCES } from '../../utils/enums'; +import { getDecisionTestDatafile } from '../../tests/decision_test_datafile'; + +import { + USER_HAS_NO_FORCED_VARIATION, + VALID_BUCKETING_ID, + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND, +} from 'log_message'; + +import { + EXPERIMENT_NOT_RUNNING, + RETURNING_STORED_VARIATION, + USER_NOT_IN_EXPERIMENT, + USER_FORCED_IN_VARIATION, + EVALUATING_AUDIENCES_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_IN_ROLLOUT, + USER_NOT_IN_ROLLOUT, + FEATURE_HAS_NO_EXPERIMENTS, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_BUCKETED_INTO_TARGETING_RULE, + NO_ROLLOUT_EXISTS, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, +} from '../decision_service/index'; + +import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from 'error_message'; +import exp from 'constants'; + +type MockLogger = ReturnType<typeof getMockLogger>; + +type MockUserProfileService = { + lookup: ReturnType<typeof vi.fn>; + save: ReturnType<typeof vi.fn>; +}; + +type DecisionServiceInstanceOpt = { + logger?: boolean; + userProfileService?: boolean; +} + +type DecisionServiceInstance = { + logger?: MockLogger; + userProfileService?: MockUserProfileService; + decisionService: DecisionService; +} + +const getDecisionService = (opt: DecisionServiceInstanceOpt = {}): DecisionServiceInstance => { + const logger = opt.logger ? getMockLogger() : undefined; + const userProfileService = opt.userProfileService ? { + lookup: vi.fn(), + save: vi.fn(), + } : undefined; + + const decisionService = new DecisionService({ + logger, + userProfileService, + UNSTABLE_conditionEvaluators: {}, + }); + + return { + logger, + userProfileService, + decisionService, + }; +}; + +const mockBucket: MockInstance<typeof bucket> = vi.hoisted(() => vi.fn()); + +vi.mock('../bucketer', () => ({ + bucket: mockBucket, +})); + +const cloneDeep = (d: any) => JSON.parse(JSON.stringify(d)); + +const testData = getTestProjectConfig(); +const testDataWithFeatures = getTestProjectConfigWithFeatures(); + +const verifyBucketCall = ( + call: number, + projectConfig: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext, + bucketIdFromAttribute = false, +) => { + const { + experimentId, + experimentKey, + userId, + trafficAllocationConfig, + experimentKeyMap, + experimentIdMap, + groupIdMap, + variationIdMap, + bucketingId, + } = mockBucket.mock.calls[call][0]; + expect(experimentId).toBe(experiment.id); + expect(experimentKey).toBe(experiment.key); + expect(userId).toBe(user.getUserId()); + expect(trafficAllocationConfig).toBe(experiment.trafficAllocation); + expect(experimentKeyMap).toBe(projectConfig.experimentKeyMap); + expect(experimentIdMap).toBe(projectConfig.experimentIdMap); + expect(groupIdMap).toBe(projectConfig.groupIdMap); + expect(variationIdMap).toBe(projectConfig.variationIdMap); + expect(bucketingId).toBe(bucketIdFromAttribute ? user.getAttributes()[CONTROL_ATTRIBUTES.BUCKETING_ID] : user.getUserId()); +}; + +describe('DecisionService', () => { + describe('getVariation', function() { + beforeEach(() => { + mockBucket.mockClear(); + }); + + it('should return the correct variation from bucketer for the given experiment key and user ID for a running experiment', () => { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const fakeDecisionResponse = { + result: '111128', + reasons: [], + }; + + const experiment = config.experimentIdMap['111127']; + + mockBucket.mockReturnValue(fakeDecisionResponse); // contains variation ID of the 'control' variation from `test_data` + + const { decisionService } = getDecisionService(); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('control'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + }); + + it('should use $opt_bucketing_id attribute as bucketing id if provided', () => { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + $opt_bucketing_id: 'test_bucketing_id', + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const fakeDecisionResponse = { + result: '111128', + reasons: [], + }; + + const experiment = config.experimentIdMap['111127']; + + mockBucket.mockReturnValue(fakeDecisionResponse); // contains variation ID of the 'control' variation from `test_data` + + const { decisionService } = getDecisionService(); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('control'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user, true); + }); + + it('should return the whitelisted variation if the user is whitelisted', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user2' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService, logger } = getDecisionService({ logger: true }); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('variationWithAudience'); + expect(mockBucket).not.toHaveBeenCalled(); + expect(logger?.debug).toHaveBeenCalledTimes(1); + expect(logger?.info).toHaveBeenCalledTimes(1); + + expect(logger?.debug).toHaveBeenNthCalledWith(1, USER_HAS_NO_FORCED_VARIATION, 'user2'); + expect(logger?.info).toHaveBeenNthCalledWith(1, USER_FORCED_IN_VARIATION, 'user2', 'variationWithAudience'); + }); + + it('should return null if the user does not meet audience conditions', () => { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user3' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService, logger } = getDecisionService({ logger: true }); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe(null); + expect(mockBucket).not.toHaveBeenCalled(); + + expect(logger?.debug).toHaveBeenNthCalledWith(1, USER_HAS_NO_FORCED_VARIATION, 'user3'); + expect(logger?.debug).toHaveBeenNthCalledWith(2, EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])); + + expect(logger?.info).toHaveBeenNthCalledWith(1, AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE'); + expect(logger?.info).toHaveBeenNthCalledWith(2, USER_NOT_IN_EXPERIMENT, 'user3', 'testExperimentWithAudiences'); + }); + + it('should return the forced variation set using setForcedVariation \ + in presence of a whitelisted variation', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user2' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService } = getDecisionService(); + + const forcedVariation = 'controlWithAudience'; + + decisionService.setForcedVariation(config, experiment.key, user.getUserId(), forcedVariation); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe(forcedVariation); + + const whitelistedVariation = experiment.forcedVariations?.[user.getUserId()]; + expect(whitelistedVariation).toBeDefined(); + expect(whitelistedVariation).not.toEqual(forcedVariation); + }); + + it('should return the forced variation set using setForcedVariation \ + even if user does not satisfy audience condition', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user3', // no attributes are set, should not satisfy audience condition 11154 + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService } = getDecisionService(); + + const forcedVariation = 'controlWithAudience'; + + decisionService.setForcedVariation(config, experiment.key, user.getUserId(), forcedVariation); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe(forcedVariation); + }); + + it('should return null if the experiment is not running', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user1' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['133337']; + + const { decisionService, logger } = getDecisionService({ logger: true }); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe(null); + expect(mockBucket).not.toHaveBeenCalled(); + expect(logger?.info).toHaveBeenCalledTimes(1); + expect(logger?.info).toHaveBeenNthCalledWith(1, EXPERIMENT_NOT_RUNNING, 'testExperimentNotRunning'); + }); + + it('should respect the sticky bucketing information for attributes when attributes.$opt_experiment_bucket_map is supplied', () => { + const fakeDecisionResponse = { + result: '111128', + reasons: [], + }; + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: any = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + mockBucket.mockReturnValue(fakeDecisionResponse); // contains variation ID of the 'control' variation from `test_data` + + const { decisionService } = getDecisionService(); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('variation'); + expect(mockBucket).not.toHaveBeenCalled(); + }); + + describe('when a user profile service is provided', function() { + beforeEach(() => { + mockBucket.mockClear(); + }); + + it('should return the previously bucketed variation', () => { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }); + + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + expect(mockBucket).not.toHaveBeenCalled(); + + expect(logger?.debug).toHaveBeenCalledTimes(1); + expect(logger?.info).toHaveBeenCalledTimes(1); + + expect(logger?.debug).toHaveBeenNthCalledWith(1, USER_HAS_NO_FORCED_VARIATION, 'decision_service_user'); + expect(logger?.info).toHaveBeenNthCalledWith(1, RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user'); + }); + + it('should bucket and save user profile if there was no prevously bucketed variation', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: {}, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should bucket if the user profile service returns null', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue(null); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should re-bucket if the stored variation is no longer valid', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: 'not valid variation', + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(logger?.debug).toHaveBeenCalledWith(USER_HAS_NO_FORCED_VARIATION, 'decision_service_user'); + expect(logger?.info).toHaveBeenCalledWith(SAVED_VARIATION_NOT_FOUND, 'decision_service_user', 'not valid variation', 'testExperiment'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should store the bucketed variation for the user', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: {}, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + + it('should log an error message and bucket if "lookup" throws an error', () => { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockImplementation(() => { + throw new Error('I am an error'); + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(logger?.debug).toHaveBeenCalledWith(USER_HAS_NO_FORCED_VARIATION, 'decision_service_user'); + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_LOOKUP_ERROR, 'decision_service_user', 'I am an error'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should log an error message if "save" throws an error', () => { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue(null); + userProfileService?.save.mockImplementation(() => { + throw new Error('I am an error'); + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_SAVE_ERROR, 'decision_service_user', 'I am an error'); + }); + + it('should respect $opt_experiment_bucket_map attribute over the userProfileService for the matching experiment id', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', // ID of the 'control' variation + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: any = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + }); + + it('should ignore attributes for a different experiment id', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', // ID of the 'control' variation + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: any = { + $opt_experiment_bucket_map: { + '122227': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + }); + + it('should use $ opt_experiment_bucket_map attribute when the userProfile contains variations for other experiments', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '122227': { + variation_id: '122229', // ID of the 'variationWithAudience' variation + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: any = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + }); + + it('should use attributes when the userProfileLookup returns null', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue(null); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: any = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + }); + }); + }); + + describe('getVariationForFeature', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return variation from the first experiment for which a variation is available', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + config, + experiment: any, + user, + shouldIgnoreUPS, + userProfileTracker + ) => { + if (experiment.key === 'exp_2') { + return { + result: 'variation_2', + reasons: [], + }; + } + return { + result: null, + reasons: [], + } + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(2); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + }); + + it('should return the variation forced for an experiment in the userContext if available', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + config, + experiment: any, + user, + shouldIgnoreUPS, + userProfileTracker + ) => { + if (experiment.key === 'exp_2') { + return { + result: 'variation_2', + reasons: [], + }; + } + return { + result: null, + reasons: [], + } + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + user.setForcedDecision( + { flagKey: 'flag_1', ruleKey: 'exp_2' }, + { variationKey: 'variation_5' } + ); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should save the variation found for an experiment in the user profile', () => { + const { decisionService, userProfileService } = getDecisionService({ userProfileService: true }); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + config, + experiment: any, + user, + shouldIgnoreUPS, + userProfileTracker: any, + ) => { + if (experiment.key === 'exp_2') { + const variation = 'variation_2'; + + userProfileTracker.userProfile[experiment.id] = { + variation_id: '5002', + }; + userProfileTracker.isProfileUpdated = true; + + return { + result: 'variation_2', + reasons: [], + }; + } + return { + result: null, + reasons: [], + } + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + userProfileService?.lookup.mockImplementation((userId: string) => { + if (userId === 'tester') { + return { + user_id: 'tester', + experiment_bucket_map: { + '2001': { + variation_id: '5001', + }, + }, + }; + } + return null; + }); + + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2001': { + variation_id: '5001', + }, + '2002': { + variation_id: '5002', + }, + }, + }); + }); + + describe('when no variation is found for any experiment and a targeted delivery \ + audience condition is satisfied', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return variation from the target delivery for which audience condition \ + is satisfied if the user is bucketed into it', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue({ + result: null, + reasons: [], + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3 + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'delivery_2') { + return { + result: '5005', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, config.experimentIdMap['3002'], user); + }); + + it('should return variation from the target delivery and use $opt_bucketing_id attribute as bucketing id', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue({ + result: null, + reasons: [], + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3 + $opt_bucketing_id: 'test_bucketing_id', + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'delivery_2') { + return { + result: '5005', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, config.experimentIdMap['3002'], user, true); + }); + + it('should skip to everyone else targeting rule if the user is not bucketed \ + into the targeted delivery for which audience condition is satisfied', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue({ + result: null, + reasons: [], + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3 + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'default-rollout-key') { + return { + result: '5007', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentIdMap['default-rollout-id'], + variation: config.variationIdMap['5007'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(2); + verifyBucketCall(0, config, config.experimentIdMap['3002'], user); + verifyBucketCall(1, config, config.experimentIdMap['default-rollout-id'], user); + }); + }); + + it('should return the forced variation for targeted delivery rule when no variation \ + is found for any experiment and a there is a forced decision \ + for a targeted delivery in the userContext', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + config, + experiment: any, + user, + shouldIgnoreUPS, + userProfileTracker + ) => { + return { + result: null, + reasons: [], + } + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + }); + + user.setForcedDecision( + { flagKey: 'flag_1', ruleKey: 'delivery_2' }, + { variationKey: 'variation_1' } + ); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + }); + + it('should return variation from the everyone else targeting rule if no variation \ + is found for any experiment or targeted delivery', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue({ + result: null, + reasons: [], + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 100, // this should not satisfy any audience condition for any targeted delivery + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'default-rollout-key') { + return { + result: '5007', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentIdMap['default-rollout-id'], + variation: config.variationIdMap['5007'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, config.experimentIdMap['default-rollout-id'], user); + }); + + it('should return null if no variation is found for any experiment, targeted delivery, or everyone else targeting rule', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue({ + result: null, + reasons: [], + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + const rolloutId = config.featureKeyMap['flag_1'].rolloutId; + config.rolloutIdMap[rolloutId].experiments = []; // remove the experiments from the rollout + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 10, + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(0); + }); + }); + + describe('getVariationsForFeatureList', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return correct results for all features in the feature list', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + config, + experiment: any, + user, + shouldIgnoreUPS, + userProfileTracker + ) => { + if (experiment.key === 'exp_2') { + return { + result: 'variation_2', + reasons: [], + }; + } else if (experiment.key === 'exp_4') { + return { + result: 'variation_flag_2', + reasons: [], + }; + } + return { + result: null, + reasons: [], + } + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + const featureList = [ + config.featureKeyMap['flag_1'], + config.featureKeyMap['flag_2'], + ]; + + const variations = decisionService.getVariationsForFeatureList(config, featureList, user); + + expect(variations[0].result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variations[1].result).toEqual({ + experiment: config.experimentKeyMap['exp_4'], + variation: config.variationIdMap['5100'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + const variations2 = decisionService.getVariationsForFeatureList(config, featureList.reverse(), user); + + expect(variations2[0].result).toEqual({ + experiment: config.experimentKeyMap['exp_4'], + variation: config.variationIdMap['5100'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variations2[1].result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should batch user profile lookup and save', () => { + const { decisionService, userProfileService } = getDecisionService({ userProfileService: true }); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + config, + experiment: any, + user, + shouldIgnoreUPS, + userProfileTracker: any, + ) => { + if (experiment.key === 'exp_2') { + userProfileTracker.userProfile[experiment.id] = { + variation_id: '5002', + }; + userProfileTracker.isProfileUpdated = true; + + return { + result: 'variation_2', + reasons: [], + }; + } else if (experiment.key === 'exp_4') { + userProfileTracker.userProfile[experiment.id] = { + variation_id: '5100', + }; + userProfileTracker.isProfileUpdated = true; + + return { + result: 'variation_flag_2', + reasons: [], + }; + } + return { + result: null, + reasons: [], + } + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + const featureList = [ + config.featureKeyMap['flag_1'], + config.featureKeyMap['flag_2'], + ]; + + const variations = decisionService.getVariationsForFeatureList(config, featureList, user); + + expect(variations[0].result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variations[1].result).toEqual({ + experiment: config.experimentKeyMap['exp_4'], + variation: config.variationIdMap['5100'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2002': { + variation_id: '5002', + }, + '2004': { + variation_id: '5100', + }, + }, + }); + }); + }); + + + describe('forced variation management', () => { + it('should return true for a valid forcedVariation in setForcedVariation', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(true); + }); + + it('should return the same variation from getVariation as was set in setVariation', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe('control'); + }); + + it('should return null from getVariation if no forced variation was set for a valid experimentKey', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + expect(config.experimentKeyMap['testExperiment']).toBeDefined(); + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + + expect(variation).toBe(null); + }); + + it('should return null from getVariation for an invalid experimentKey', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + expect(config.experimentKeyMap['definitely_not_valid_exp_key']).not.toBeDefined(); + const variation = decisionService.getForcedVariation(config, 'definitely_not_valid_exp_key', 'user1').result; + + expect(variation).toBe(null); + }); + + it('should return null when a forced decision is set on another experiment key', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + decisionService.setForcedVariation(config, 'testExperiment', 'user1', 'control'); + const variation = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + expect(variation).toBe(null); + }); + + it('should not set forced variation for an invalid variation key and return false', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const wasSet = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'definitely_not_valid_variation_key' + ); + + expect(wasSet).toBe(false); + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe(null); + }); + + it('should reset the forcedVariation if null is passed to setForcedVariation', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + + expect(didSetVariation).toBe(true); + + let variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe('control'); + + const didSetVariationAgain = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + null + ); + + expect(didSetVariationAgain).toBe(true); + + variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe(null); + }); + + it('should be able to add variations for multiple experiments for one user', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperimentLaunched', + 'user1', + 'controlLaunched' + ); + expect(didSetVariation2).toBe(true); + + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + const variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + expect(variation).toBe('control'); + expect(variation2).toBe('controlLaunched'); + }); + + it('should be able to forced variation to same experiment for multiple users', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user2', + 'variation' + ); + expect(didSetVariation2).toBe(true); + + const variationControl = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + const variationVariation = decisionService.getForcedVariation(config, 'testExperiment', 'user2').result; + + expect(variationControl).toBe('control'); + expect(variationVariation).toBe('variation'); + }); + + it('should be able to reset a variation for a user with multiple experiments', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + // Set the first time + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperimentLaunched', + 'user1', + 'controlLaunched' + ); + expect(didSetVariation2).toBe(true); + + let variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + let variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe('control'); + expect(variation2).toBe('controlLaunched'); + + // Reset for one of the experiments + const didSetVariationAgain = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'variation' + ); + expect(didSetVariationAgain).toBe(true); + + variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe('variation'); + expect(variation2).toBe('controlLaunched'); + }); + + it('should be able to unset a variation for a user with multiple experiments', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + // Set the first time + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperimentLaunched', + 'user1', + 'controlLaunched' + ); + expect(didSetVariation2).toBe(true); + + let variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + let variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe('control'); + expect(variation2).toBe('controlLaunched'); + + // Unset for one of the experiments + decisionService.setForcedVariation(config, 'testExperiment', 'user1', null); + + variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe(null); + expect(variation2).toBe('controlLaunched'); + }); + + it('should return false for an empty variation key', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation(config, 'testExperiment', 'user1', ''); + expect(didSetVariation).toBe(false); + }); + + it('should return null when a variation was previously set, and that variation no longer exists on the config object', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(true); + + const newDatafile = cloneDeep(testData); + // Remove 'control' variation from variations, traffic allocation, and datafile forcedVariations. + newDatafile.experiments[0].variations = [ + { + key: 'variation', + id: '111129', + }, + ]; + newDatafile.experiments[0].trafficAllocation = [ + { + entityId: '111129', + endOfRange: 9000, + }, + ]; + newDatafile.experiments[0].forcedVariations = { + user1: 'variation', + user2: 'variation', + }; + // Now the only variation in testExperiment is 'variation' + const newConfigObj = createProjectConfig(newDatafile); + const forcedVar = decisionService.getForcedVariation(newConfigObj, 'testExperiment', 'user1').result; + expect(forcedVar).toBe(null); + }); + + it("should return null when a variation was previously set, and that variation's experiment no longer exists on the config object", function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(true); + + const newConfigObj = createProjectConfig(cloneDeep(testDataWithFeatures)); + const forcedVar = decisionService.getForcedVariation(newConfigObj, 'testExperiment', 'user1').result; + expect(forcedVar).toBe(null); + }); + + it('should return false from setForcedVariation and not set for invalid experiment key', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'definitelyNotAValidExperimentKey', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(false); + + const variation = decisionService.getForcedVariation( + config, + 'definitelyNotAValidExperimentKey', + 'user1' + ).result; + expect(variation).toBe(null); + }); + }); +}); diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index b723d118b..8dd68aa88 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2017-2022, 2024, Optimizely + * Copyright 2017-2022, 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -658,16 +658,6 @@ describe('lib/core/decision_service', function() { }); }); - describe('checkIfExperimentIsActive', function() { - it('should return true if experiment is running', function() { - assert.isTrue(decisionServiceInstance.checkIfExperimentIsActive(configObj, 'testExperiment')); - }); - - it('should return false when experiment is not running', function() { - assert.isFalse(decisionServiceInstance.checkIfExperimentIsActive(configObj, 'testExperimentNotRunning')); - }); - }); - describe('checkIfUserIsInAudience', function() { var __audienceEvaluateSpy; @@ -1269,214 +1259,12 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in the experiment the feature is attached to', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; - var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Running', - key: 'testing_my_feature', - id: '594098', - variations: [ - { - id: '594096', - variables: [ - { - id: '4792309476491264', - value: '2', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me NOW', - }, - { - id: '6199684360044544', - value: '20.25', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 1, "text": "first variation"}', - }, - ], - featureEnabled: true, - key: 'variation', - }, - { - id: '594097', - variables: [ - { - id: '4792309476491264', - value: '10', - }, - { - id: '5073784453201920', - value: 'false', - }, - { - id: '5636734406623232', - value: 'Buy me', - }, - { - id: '6199684360044544', - value: '50.55', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 2, "text": "second variation"}', - }, - ], - featureEnabled: true, - key: 'control', - }, - { - id: '594099', - variables: [ - { - id: '4792309476491264', - value: '40', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me Later', - }, - { - id: '6199684360044544', - value: '99.99', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 3, "text": "third variation"}', - }, - ], - featureEnabled: false, - key: 'variation2', - }, - ], - audienceIds: [], - trafficAllocation: [ - { endOfRange: 5000, entityId: '594096' }, - { endOfRange: 10000, entityId: '594097' }, - ], - layerId: '594093', - variationKeyMap: { - control: { - id: '594097', - variables: [ - { - id: '4792309476491264', - value: '10', - }, - { - id: '5073784453201920', - value: 'false', - }, - { - id: '5636734406623232', - value: 'Buy me', - }, - { - id: '6199684360044544', - value: '50.55', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 2, "text": "second variation"}', - }, - ], - featureEnabled: true, - key: 'control', - }, - variation: { - id: '594096', - variables: [ - { - id: '4792309476491264', - value: '2', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me NOW', - }, - { - id: '6199684360044544', - value: '20.25', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 1, "text": "first variation"}', - }, - ], - featureEnabled: true, - key: 'variation', - }, - variation2: { - id: '594099', - variables: [ - { - id: '4792309476491264', - value: '40', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me Later', - }, - { - id: '6199684360044544', - value: '99.99', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 3, "text": "third variation"}', - }, - ], - featureEnabled: false, - key: 'variation2', - }, - }, - }, - variation: { - id: '594096', - variables: [ - { - id: '4792309476491264', - value: '2', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me NOW', - }, - { - id: '6199684360044544', - value: '20.25', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 1, "text": "first variation"}', - }, - ], - featureEnabled: true, - key: 'variation', - }, + const expectedDecision = { + experiment: configObj.experimentIdMap['594098'], + variation: configObj.variationIdMap['594096'], decisionSource: DECISION_SOURCES.FEATURE_TEST, }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWith( getVariationStub, @@ -1540,42 +1328,11 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in an experiment in a group', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Running', - key: 'exp_with_group', - id: '595010', - variations: [ - { id: '595008', variables: [], key: 'var' }, - { id: '595009', variables: [], key: 'con' }, - ], - audienceIds: [], - trafficAllocation: [ - { endOfRange: 5000, entityId: '595008' }, - { endOfRange: 10000, entityId: '595009' }, - ], - layerId: '595005', - groupId: '595024', - variationKeyMap: { - con: { - id: '595009', - variables: [], - key: 'con', - }, - var: { - id: '595008', - variables: [], - key: 'var', - }, - }, - }, - variation: { - id: '595008', - variables: [], - key: 'var', - }, + experiment: configObj.experimentIdMap['595010'], + variation: configObj.variationIdMap['595008'], decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; + }; + assert.deepEqual(decision, expectedDecision); }); }); @@ -1649,103 +1406,11 @@ describe('lib/core/decision_service', function() { }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Not started', - key: '594031', - id: '594031', - isRollout: true, - variations: [ - { - id: '594032', - variables: [ - { - id: '4919852825313280', - value: 'true', - }, - { - id: '5482802778734592', - value: '395', - }, - { - id: '6045752732155904', - value: '4.99', - }, - { - id: '6327227708866560', - value: 'Hello audience', - }, - { - id: "8765345281230956", - value: '{ "count": 2, "message": "Hello audience" }', - }, - ], - featureEnabled: true, - key: '594032', - }, - ], - variationKeyMap: { - 594032: { - id: '594032', - variables: [ - { - id: '4919852825313280', - value: 'true', - }, - { - id: '5482802778734592', - value: '395', - }, - { - id: '6045752732155904', - value: '4.99', - }, - { - id: '6327227708866560', - value: 'Hello audience', - }, - { - id: "8765345281230956", - value: '{ "count": 2, "message": "Hello audience" }', - }, - ], - featureEnabled: true, - key: '594032', - }, - }, - audienceIds: ['594017'], - trafficAllocation: [{ endOfRange: 5000, entityId: '594032' }], - layerId: '594030', - }, - variation: { - id: '594032', - variables: [ - { - id: '4919852825313280', - value: 'true', - }, - { - id: '5482802778734592', - value: '395', - }, - { - id: '6045752732155904', - value: '4.99', - }, - { - id: '6327227708866560', - value: 'Hello audience', - }, - { - id: "8765345281230956", - value: '{ "count": 2, "message": "Hello audience" }', - }, - ], - featureEnabled: true, - key: '594032', - }, + experiment: configObj.experimentIdMap['594031'], + variation: configObj.variationIdMap['594032'], decisionSource: DECISION_SOURCES.ROLLOUT, }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( mockLogger.debug, @@ -1784,103 +1449,11 @@ describe('lib/core/decision_service', function() { }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Not started', - key: '594037', - id: '594037', - isRollout: true, - variations: [ - { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - ], - audienceIds: [], - trafficAllocation: [{ endOfRange: 0, entityId: '594038' }], - layerId: '594030', - variationKeyMap: { - 594038: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - }, - }, - variation: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, + experiment: configObj.experimentIdMap['594037'], + variation: configObj.variationIdMap['594038'], decisionSource: DECISION_SOURCES.ROLLOUT, - }; + }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( mockLogger.debug, @@ -1964,103 +1537,11 @@ describe('lib/core/decision_service', function() { }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Not started', - key: '594037', - id: '594037', - isRollout: true, - variations: [ - { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - ], - audienceIds: [], - trafficAllocation: [{ endOfRange: 0, entityId: '594038' }], - layerId: '594030', - variationKeyMap: { - 594038: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - }, - }, - variation: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, + experiment: configObj.experimentIdMap['594037'], + variation: configObj.variationIdMap['594038'], decisionSource: DECISION_SOURCES.ROLLOUT, }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( mockLogger.debug, @@ -2111,72 +1592,11 @@ describe('lib/core/decision_service', function() { }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - trafficAllocation: [ - { - endOfRange: 10000, - entityId: '599057', - }, - ], - layerId: '599055', - forcedVariations: {}, - audienceIds: [], - isRollout: true, - variations: [ - { - key: '599057', - id: '599057', - featureEnabled: true, - variables: [ - { - id: '4937719889264640', - value: '200', - }, - { - id: '6345094772817920', - value: "i'm a rollout", - }, - ], - }, - ], - status: 'Not started', - key: '599056', - id: '599056', - variationKeyMap: { - 599057: { - key: '599057', - id: '599057', - featureEnabled: true, - variables: [ - { - id: '4937719889264640', - value: '200', - }, - { - id: '6345094772817920', - value: "i'm a rollout", - }, - ], - }, - }, - }, - variation: { - key: '599057', - id: '599057', - featureEnabled: true, - variables: [ - { - id: '4937719889264640', - value: '200', - }, - { - id: '6345094772817920', - value: "i'm a rollout", - }, - ], - }, + experiment: configObj.experimentIdMap['599056'], + variation: configObj.variationIdMap['599057'], decisionSource: DECISION_SOURCES.ROLLOUT, }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( mockLogger.debug, @@ -2324,29 +1744,7 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066'); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); @@ -2369,29 +1767,7 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066', mockLogger); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); @@ -2507,29 +1883,7 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066'); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); @@ -2552,29 +1906,7 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066', mockLogger); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 21a63b763..386606cc9 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2017-2022, 2024, Optimizely + * Copyright 2017-2022, 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -110,7 +110,7 @@ export interface DecisionObj { } interface DecisionServiceOptions { - userProfileService: UserProfileService | null; + userProfileService?: UserProfileService; logger?: LoggerFacade; UNSTABLE_conditionEvaluators: unknown; } @@ -143,13 +143,13 @@ export class DecisionService { private logger?: LoggerFacade; private audienceEvaluator: AudienceEvaluator; private forcedVariationMap: { [key: string]: { [id: string]: string } }; - private userProfileService: UserProfileService | null; + private userProfileService?: UserProfileService; constructor(options: DecisionServiceOptions) { this.logger = options.logger; this.audienceEvaluator = createAudienceEvaluator(options.UNSTABLE_conditionEvaluators, this.logger); this.forcedVariationMap = {}; - this.userProfileService = options.userProfileService || null; + this.userProfileService = options.userProfileService; } /** @@ -170,11 +170,14 @@ export class DecisionService { ): DecisionResponse<string | null> { const userId = user.getUserId(); const attributes = user.getAttributes(); + // by default, the bucketing ID should be the user ID const bucketingId = this.getBucketingId(userId, attributes); - const decideReasons: (string | number)[][] = []; const experimentKey = experiment.key; - if (!this.checkIfExperimentIsActive(configObj, experimentKey)) { + + const decideReasons: (string | number)[][] = []; + + if (!isActive(configObj, experimentKey)) { this.logger?.info(EXPERIMENT_NOT_RUNNING, experimentKey); decideReasons.push([EXPERIMENT_NOT_RUNNING, experimentKey]); return { @@ -182,6 +185,7 @@ export class DecisionService { reasons: decideReasons, }; } + const decisionForcedVariation = this.getForcedVariation(configObj, experimentKey, userId); decideReasons.push(...decisionForcedVariation.reasons); const forcedVariationKey = decisionForcedVariation.result; @@ -192,6 +196,7 @@ export class DecisionService { reasons: decideReasons, }; } + const decisionWhitelistedVariation = this.getWhitelistedVariation(experiment, userId); decideReasons.push(...decisionWhitelistedVariation.reasons); let variation = decisionWhitelistedVariation.result; @@ -202,7 +207,6 @@ export class DecisionService { }; } - // check for sticky bucketing if decide options do not include shouldIgnoreUPS if (!shouldIgnoreUPS) { variation = this.getStoredVariation(configObj, experiment, userId, userProfileTracker.userProfile); @@ -349,16 +353,6 @@ export class DecisionService { return { ...userProfile.experiment_bucket_map, ...attributeExperimentBucketMap as any }; } - /** - * Checks whether the experiment is running - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {string} experimentKey Key of experiment being validated - * @return {boolean} True if experiment is running - */ - private checkIfExperimentIsActive(configObj: ProjectConfig, experimentKey: string): boolean { - return isActive(configObj, experimentKey); - } - /** * Checks if user is whitelisted into any variation and return that variation if so * @param {Experiment} experiment @@ -621,7 +615,7 @@ export class DecisionService { isProfileUpdated: false, userProfile: null, } - const shouldIgnoreUPS = options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; + const shouldIgnoreUPS = !!options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; if(!shouldIgnoreUPS) { userProfileTracker.userProfile = this.resolveExperimentBucketMap(userId, attributes); @@ -661,10 +655,10 @@ export class DecisionService { } if(!shouldIgnoreUPS) { - this.saveUserProfile(userId, userProfileTracker) + this.saveUserProfile(userId, userProfileTracker); } - return decisions + return decisions; } @@ -968,7 +962,7 @@ export class DecisionService { * @param {string} experimentKey Key representing the experiment id * @throws If the user id is not valid or not in the forced variation map */ - removeForcedVariation(userId: string, experimentId: string, experimentKey: string): void { + private removeForcedVariation(userId: string, experimentId: string, experimentKey: string): void { if (!userId) { throw new OptimizelyError(INVALID_USER_ID); } @@ -1176,7 +1170,7 @@ export class DecisionService { } } - getVariationFromExperimentRule( + private getVariationFromExperimentRule( configObj: ProjectConfig, flagKey: string, rule: Experiment, @@ -1207,7 +1201,7 @@ export class DecisionService { }; } - getVariationFromDeliveryRule( + private getVariationFromDeliveryRule( configObj: ProjectConfig, flagKey: string, rules: Experiment[], diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index c4efb2c67..cd74a2d00 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -262,7 +262,7 @@ describe('lib/optimizely', function() { }); sinon.assert.calledWith(decisionService.createDecisionService, { - userProfileService: null, + userProfileService: undefined, logger: createdLogger, UNSTABLE_conditionEvaluators: undefined, }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 9ca0c6089..bf8e6c717 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2024, Optimizely + * Copyright 2020-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ import { NODE_CLIENT_ENGINE, CLIENT_VERSION, } from '../utils/enums'; -import { Fn } from '../utils/type'; +import { Fn, Maybe } from '../utils/type'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; @@ -211,8 +211,7 @@ export default class Optimizely extends BaseService implements Client { this.odpManager = config.odpManager; - - let userProfileService: UserProfileService | null = null; + let userProfileService: Maybe<UserProfileService> = undefined; if (config.userProfileService) { try { if (userProfileServiceValidator.validate(config.userProfileService)) { diff --git a/lib/tests/decision_test_datafile.ts b/lib/tests/decision_test_datafile.ts new file mode 100644 index 000000000..84c72de90 --- /dev/null +++ b/lib/tests/decision_test_datafile.ts @@ -0,0 +1,468 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// flag id starts from 1000 +// experiment id starts from 2000 +// rollout experiment id starts from 3000 +// audience id starts from 4000 +// variation id starts from 5000 +// variable id starts from 6000 +// attribute id starts from 7000 + +const testDatafile = { + accountId: "24535200037", + projectId: "5088239376138240", + revision: "21", + attributes: [ + { + id: "7001", + key: "age" + } + ], + audiences: [ + { + name: "age_22", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4001" + }, + { + name: "age_60", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4002" + }, + { + name: "age_90", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4003" + }, + { + id: "$opt_dummy_audience", + name: "Optimizely-Generated Audience for Backwards Compatibility", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]" + } + ], + version: "4", + events: [], + integrations: [], + anonymizeIP: true, + botFiltering: false, + typedAudiences: [ + { + name: "age_22", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 22 + } + ] + ] + ], + id: "4001" + }, + { + name: "age_60", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 60 + } + ] + ] + ], + id: "4002" + }, + { + name: "age_90", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 90 + } + ] + ] + ], + id: "4003" + }, + ], + variables: [], + environmentKey: "production", + sdkKey: "sdk_key", + featureFlags: [ + { + id: "1001", + key: "flag_1", + rolloutId: "rollout-371334-671741182375276", + experimentIds: [ + "2001", + "2002", + "2003" + ], + variables: [ + { + id: "6001", + key: "integer_variable", + "type": "integer", + "defaultValue": "0" + } + ] + }, + { + id: "1002", + key: "flag_2", + "rolloutId": "rollout-374517-931741182375293", + experimentIds: [ + "2004" + ], + "variables": [] + } + ], + "rollouts": [ + { + id: "rollout-371334-671741182375276", + experiments: [ + { + id: "3001", + key: "delivery_1", + status: "Running", + layerId: "9300001480454", + variations: [ + { + id: "5004", + key: "variation_4", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "4" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5004", + endOfRange: 1500 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4001" + ], + audienceConditions: [ + "or", + "4001" + ] + }, + { + id: "3002", + key: "delivery_2", + status: "Running", + layerId: "9300001480455", + variations: [ + { + id: "5005", + key: "variation_5", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "5" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5005", + endOfRange: 4000 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4002" + ], + audienceConditions: [ + "or", + "4002" + ] + }, + { + id: "3003", + key: "delivery_3", + status: "Running", + layerId: "9300001495996", + variations: [ + { + id: "5006", + key: "variation_6", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "6" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5006", + endOfRange: 8000 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4003" + ], + audienceConditions: [ + "or", + "4003" + ] + }, + { + id: "default-rollout-id", + key: "default-rollout-key", + status: "Running", + layerId: "rollout-371334-671741182375276", + variations: [ + { + id: "5007", + key: "variation_7", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "7" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5007", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + }, + ] + }, + { + id: "rollout-374517-931741182375293", + experiments: [ + { + id: "default-rollout-374517-931741182375293", + key: "default-rollout-374517-931741182375293", + status: "Running", + layerId: "rollout-374517-931741182375293", + variations: [ + { + id: "1177722", + key: "off", + featureEnabled: false, + variables: [] + } + ], + trafficAllocation: [ + { + "entityId": "1177722", + "endOfRange": 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + } + ] + }, + ], + experiments: [ + { + id: "2001", + key: "exp_1", + status: "Running", + layerId: "9300001480444", + variations: [ + { + id: "5001", + key: "variation_1", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "1" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5001", + endOfRange: 5000 + }, + { + entityId: "5001", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4001" + ], + audienceConditions: [ + "or", + "4001" + ] + }, + { + id: "2002", + key: "exp_2", + status: "Running", + layerId: "9300001480448", + variations: [ + { + id: "5002", + key: "variation_2", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "2" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5002", + endOfRange: 5000 + }, + { + entityId: "5002", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + }, + { + id: "2003", + key: "exp_3", + status: "Running", + layerId: "9300001480451", + variations: [ + { + id: "5003", + key: "variation_3", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "3" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5003", + endOfRange: 5000 + }, + { + entityId: "5003", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + }, + { + id: "2004", + key: "exp_4", + status: "Running", + layerId: "9300001497754", + variations: [ + { + id: "5100", + key: "variation_flag_2", + featureEnabled: true, + variables: [] + } + ], + trafficAllocation: [ + { + entityId: "5100", + endOfRange: 5000 + }, + { + entityId: "5100", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + } + ], + groups: [] +} + +export const getDecisionTestDatafile = (): any => { + return JSON.parse(JSON.stringify(testDatafile)); +} From aaf83d7d8a2d8d29d700f50e3a15936e986925ba Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 14 Mar 2025 00:12:24 +0600 Subject: [PATCH 136/200] add default empty options for factories (#1016) all individual fields of certain factory option objects are optional, so making the whole option object optional by using a defualt empty object --- lib/entrypoint.test-d.ts | 6 +++--- lib/event_processor/event_processor_factory.browser.ts | 4 ++-- lib/event_processor/event_processor_factory.node.ts | 4 ++-- .../event_processor_factory.react_native.ts | 4 ++-- lib/odp/odp_manager_factory.browser.ts | 4 ++-- lib/odp/odp_manager_factory.node.ts | 4 ++-- lib/odp/odp_manager_factory.react_native.ts | 4 ++-- lib/vuid/vuid_manager_factory.browser.ts | 7 ++++--- lib/vuid/vuid_manager_factory.node.ts | 4 ++-- lib/vuid/vuid_manager_factory.react_native.ts | 6 +++--- 10 files changed, 24 insertions(+), 23 deletions(-) diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts index a9a782522..ee6408344 100644 --- a/lib/entrypoint.test-d.ts +++ b/lib/entrypoint.test-d.ts @@ -68,13 +68,13 @@ export type Entrypoint = { eventDispatcher: EventDispatcher; getSendBeaconEventDispatcher: () => EventDispatcher; createForwardingEventProcessor: (eventDispatcher?: EventDispatcher) => OpaqueEventProcessor; - createBatchEventProcessor: (options: BatchEventProcessorOptions) => OpaqueEventProcessor; + createBatchEventProcessor: (options?: BatchEventProcessorOptions) => OpaqueEventProcessor; // odp manager related exports - createOdpManager: (options: OdpManagerOptions) => OpaqueOdpManager; + createOdpManager: (options?: OdpManagerOptions) => OpaqueOdpManager; // vuid manager related exports - createVuidManager: (options: VuidManagerOptions) => OpaqueVuidManager; + createVuidManager: (options?: VuidManagerOptions) => OpaqueVuidManager; // logger related exports LogLevel: typeof LogLevel; diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index 0ce034dc7..7270d9b86 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ export const createForwardingEventProcessor = ( const identity = <T>(v: T): T => v; export const createBatchEventProcessor = ( - options: BatchEventProcessorOptions + options: BatchEventProcessorOptions = {} ): OpaqueEventProcessor => { const localStorageCache = new LocalStorageCache<EventWithId>(); const eventStore = new SyncPrefixCache<EventWithId, EventWithId>( diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index 4671ce3a3..29ccebade 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ export const createForwardingEventProcessor = ( }; export const createBatchEventProcessor = ( - options: BatchEventProcessorOptions + options: BatchEventProcessorOptions = {} ): OpaqueEventProcessor => { const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : undefined; diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index fefb3f816..0fc5ed8ed 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ const getDefaultEventStore = () => { } export const createBatchEventProcessor = ( - options: BatchEventProcessorOptions + options: BatchEventProcessorOptions = {} ): OpaqueEventProcessor => { const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : getDefaultEventStore(); diff --git a/lib/odp/odp_manager_factory.browser.ts b/lib/odp/odp_manager_factory.browser.ts index 2090dcb87..bf56d82cd 100644 --- a/lib/odp/odp_manager_factory.browser.ts +++ b/lib/odp/odp_manager_factory.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_ export const BROWSER_DEFAULT_API_TIMEOUT = 10_000; -export const createOdpManager = (options: OdpManagerOptions): OpaqueOdpManager => { +export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpManager => { const segmentRequestHandler = new BrowserRequestHandler({ timeout: options.segmentsApiTimeout || BROWSER_DEFAULT_API_TIMEOUT, }); diff --git a/lib/odp/odp_manager_factory.node.ts b/lib/odp/odp_manager_factory.node.ts index 35d223462..e59c657bd 100644 --- a/lib/odp/odp_manager_factory.node.ts +++ b/lib/odp/odp_manager_factory.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ export const NODE_DEFAULT_API_TIMEOUT = 10_000; export const NODE_DEFAULT_BATCH_SIZE = 10; export const NODE_DEFAULT_FLUSH_INTERVAL = 1000; -export const createOdpManager = (options: OdpManagerOptions): OpaqueOdpManager => { +export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpManager => { const segmentRequestHandler = new NodeRequestHandler({ timeout: options.segmentsApiTimeout || NODE_DEFAULT_API_TIMEOUT, }); diff --git a/lib/odp/odp_manager_factory.react_native.ts b/lib/odp/odp_manager_factory.react_native.ts index 854ba32bc..c76312d6d 100644 --- a/lib/odp/odp_manager_factory.react_native.ts +++ b/lib/odp/odp_manager_factory.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ export const RN_DEFAULT_API_TIMEOUT = 10_000; export const RN_DEFAULT_BATCH_SIZE = 10; export const RN_DEFAULT_FLUSH_INTERVAL = 1000; -export const createOdpManager = (options: OdpManagerOptions): OpaqueOdpManager => { +export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpManager => { const segmentRequestHandler = new BrowserRequestHandler({ timeout: options.segmentsApiTimeout || RN_DEFAULT_API_TIMEOUT, }); diff --git a/lib/vuid/vuid_manager_factory.browser.ts b/lib/vuid/vuid_manager_factory.browser.ts index 97e94dc2e..8aee22f97 100644 --- a/lib/vuid/vuid_manager_factory.browser.ts +++ b/lib/vuid/vuid_manager_factory.browser.ts @@ -1,5 +1,5 @@ /** -* Copyright 2024, Optimizely +* Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,11 @@ import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_m export const vuidCacheManager = new VuidCacheManager(); -export const createVuidManager = (options: VuidManagerOptions): OpaqueVuidManager => { +export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidManager => { return wrapVuidManager(new DefaultVuidManager({ vuidCacheManager, vuidCache: options.vuidCache || new LocalStorageCache<string>(), enableVuid: options.enableVuid })); -} +}; + diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts index e8de3e564..54dd2dbaa 100644 --- a/lib/vuid/vuid_manager_factory.node.ts +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -1,5 +1,5 @@ /** -* Copyright 2024, Optimizely +* Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,6 @@ import { OpaqueVuidManager, VuidManagerOptions } from './vuid_manager_factory'; export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; -export const createVuidManager = (options: VuidManagerOptions): OpaqueVuidManager => { +export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidManager => { throw new Error(VUID_IS_NOT_SUPPORTED_IN_NODEJS); }; diff --git a/lib/vuid/vuid_manager_factory.react_native.ts b/lib/vuid/vuid_manager_factory.react_native.ts index 51b3f754b..0aeb1c537 100644 --- a/lib/vuid/vuid_manager_factory.react_native.ts +++ b/lib/vuid/vuid_manager_factory.react_native.ts @@ -1,5 +1,5 @@ /** -* Copyright 2024, Optimizely +* Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,10 @@ import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_m export const vuidCacheManager = new VuidCacheManager(); -export const createVuidManager = (options: VuidManagerOptions): OpaqueVuidManager => { +export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidManager => { return wrapVuidManager(new DefaultVuidManager({ vuidCacheManager, vuidCache: options.vuidCache || new AsyncStorageCache<string>(), enableVuid: options.enableVuid })); -} +}; From df094ade7f3c11fd8792eda1747d2de29520fc19 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 27 Mar 2025 13:58:23 +0600 Subject: [PATCH 137/200] [FSSDK-11238] Test conversions notification_center and utils (#1020) --- .../bucketer/bucket_value_generator.spec.ts | 12 +- lib/core/bucketer/index.spec.ts | 11 +- lib/notification_center/index.spec.ts | 606 ++++++++++++++++++ lib/project_config/project_config.spec.ts | 3 - lib/utils/attributes_validator/index.spec.ts | 115 ++++ lib/utils/config_validator/index.spec.ts | 65 ++ lib/utils/event_tag_utils/index.spec.ts | 88 +++ lib/utils/event_tags_validator/index.spec.ts | 63 ++ lib/utils/json_schema_validator/index.spec.ts | 49 ++ lib/utils/semantic_version/index.spec.ts | 112 ++++ .../string_value_validator/index.spec.ts | 32 + .../index.spec.ts | 96 +++ 12 files changed, 1243 insertions(+), 9 deletions(-) create mode 100644 lib/notification_center/index.spec.ts create mode 100644 lib/utils/attributes_validator/index.spec.ts create mode 100644 lib/utils/config_validator/index.spec.ts create mode 100644 lib/utils/event_tag_utils/index.spec.ts create mode 100644 lib/utils/event_tags_validator/index.spec.ts create mode 100644 lib/utils/json_schema_validator/index.spec.ts create mode 100644 lib/utils/semantic_version/index.spec.ts create mode 100644 lib/utils/string_value_validator/index.spec.ts create mode 100644 lib/utils/user_profile_service_validator/index.spec.ts diff --git a/lib/core/bucketer/bucket_value_generator.spec.ts b/lib/core/bucketer/bucket_value_generator.spec.ts index a7662e1f0..e68db6348 100644 --- a/lib/core/bucketer/bucket_value_generator.spec.ts +++ b/lib/core/bucketer/bucket_value_generator.spec.ts @@ -36,8 +36,14 @@ describe('generateBucketValue', () => { it('should return an error if it cannot generate the hash value', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(() => generateBucketValue(null)).toThrowError( - new OptimizelyError(INVALID_BUCKETING_ID) - ); + expect(() => generateBucketValue(null)).toThrow(OptimizelyError); + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + generateBucketValue(null); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_BUCKETING_ID); + } }); }); diff --git a/lib/core/bucketer/index.spec.ts b/lib/core/bucketer/index.spec.ts index 36f23b2eb..b3aac5158 100644 --- a/lib/core/bucketer/index.spec.ts +++ b/lib/core/bucketer/index.spec.ts @@ -198,9 +198,14 @@ describe('including groups: random', () => { const bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; - expect(() => bucketer.bucket(bucketerParamsWithInvalidGroupId)).toThrowError( - new OptimizelyError(INVALID_GROUP_ID, '6969') - ); + expect(()=> bucketer.bucket(bucketerParamsWithInvalidGroupId)).toThrow(OptimizelyError); + + try { + bucketer.bucket(bucketerParamsWithInvalidGroupId); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_GROUP_ID); + } }); }); diff --git a/lib/notification_center/index.spec.ts b/lib/notification_center/index.spec.ts new file mode 100644 index 000000000..4ba54a0c3 --- /dev/null +++ b/lib/notification_center/index.spec.ts @@ -0,0 +1,606 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeEach, it, vi, expect } from 'vitest'; +import { createNotificationCenter, DefaultNotificationCenter } from './'; +import { + ActivateListenerPayload, + DecisionListenerPayload, + LogEventListenerPayload, + NOTIFICATION_TYPES, + TrackListenerPayload, + OptimizelyConfigUpdateListenerPayload, +} from './type'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { LoggerFacade } from '../logging/logger'; + +describe('addNotificationListener', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should return -1 if notification type is not a valid type', () => { + const INVALID_LISTENER_TYPE = 'INVALID_LISTENER_TYPE' as const; + const mockFn = vi.fn(); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const listenerId = notificationCenterInstance.addNotificationListener(INVALID_LISTENER_TYPE, mockFn); + + expect(listenerId).toBe(-1); + }); + + it('should return an id (listernId) > 0 of the notification listener if callback is not already added', () => { + const activateCallback = vi.fn(); + const decisionCallback = vi.fn(); + const logEventCallback = vi.fn(); + const configUpdateCallback = vi.fn(); + const trackCallback = vi.fn(); + // store a listenerId for each type + const activateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallback + ); + const decisionListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallback + ); + const logEventListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallback + ); + const configUpdateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallback + ); + const trackListenerId = notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallback); + + expect(activateListenerId).toBeGreaterThan(0); + expect(decisionListenerId).toBeGreaterThan(0); + expect(logEventListenerId).toBeGreaterThan(0); + expect(configUpdateListenerId).toBeGreaterThan(0); + expect(trackListenerId).toBeGreaterThan(0); + }); +}); + +describe('removeNotificationListener', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should return false if listernId does not exist', () => { + const notListenerId = notificationCenterInstance.removeNotificationListener(5); + + expect(notListenerId).toBe(false); + }); + + it('should return true when eixsting listener is removed', () => { + const activateCallback = vi.fn(); + const decisionCallback = vi.fn(); + const logEventCallback = vi.fn(); + const configUpdateCallback = vi.fn(); + const trackCallback = vi.fn(); + // add listeners for each type + const activateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallback + ); + const decisionListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallback + ); + const logEventListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallback + ); + const configListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallback + ); + const trackListenerId = notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallback); + // remove listeners for each type + const activateListenerRemoved = notificationCenterInstance.removeNotificationListener(activateListenerId); + const decisionListenerRemoved = notificationCenterInstance.removeNotificationListener(decisionListenerId); + const logEventListenerRemoved = notificationCenterInstance.removeNotificationListener(logEventListenerId); + const trackListenerRemoved = notificationCenterInstance.removeNotificationListener(trackListenerId); + const configListenerRemoved = notificationCenterInstance.removeNotificationListener(configListenerId); + + expect(activateListenerRemoved).toBe(true); + expect(decisionListenerRemoved).toBe(true); + expect(logEventListenerRemoved).toBe(true); + expect(trackListenerRemoved).toBe(true); + expect(configListenerRemoved).toBe(true); + }); + it('should only remove the specified listener', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + // register listeners for each type + const activateListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallbackSpy1 + ); + const decisionListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallbackSpy1 + ); + const logeventlistenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallbackSpy1 + ); + const configUpdateListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + const trackListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.TRACK, + trackCallbackSpy1 + ); + // register second listeners for each type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // remove first listener + const activateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(activateListenerId1); + const decisionListenerRemoved1 = notificationCenterInstance.removeNotificationListener(decisionListenerId1); + const logEventListenerRemoved1 = notificationCenterInstance.removeNotificationListener(logeventlistenerId1); + const configUpdateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(configUpdateListenerId1); + const trackListenerRemoved1 = notificationCenterInstance.removeNotificationListener(trackListenerId1); + // send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateListenerRemoved1).toBe(true); + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).toHaveBeenCalledTimes(1); + expect(decisionListenerRemoved1).toBe(true); + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).toHaveBeenCalledTimes(1); + expect(logEventListenerRemoved1).toBe(true); + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).toHaveBeenCalledTimes(1); + expect(configUpdateListenerRemoved1).toBe(true); + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).toHaveBeenCalledTimes(1); + expect(trackListenerRemoved1).toBe(true); + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).toHaveBeenCalledTimes(1); + }); +}); + +describe('clearAllNotificationListeners', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should remove all notification listeners for all types', () => { + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove all listeners + notificationCenterInstance.clearAllNotificationListeners(); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + }); +}); + +describe('clearNotificationListeners', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should remove all notification listeners for the ACTIVATE type', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + //add 2 different listeners for ACTIVATE + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + // remove ACTIVATE listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the DECISION type', () => { + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + //add 2 different listeners for DECISION + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + // remove DECISION listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the LOG_EVENT type', () => { + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + //add 2 different listeners for LOG_EVENT + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + // remove LOG_EVENT listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the OPTIMIZELY_CONFIG_UPDATE type', () => { + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + //add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + // remove OPTIMIZELY_CONFIG_UPDATE listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + // trigger send notifications + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the TRACK type', () => { + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + //add 2 different listeners for TRACK + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // remove TRACK listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should only remove ACTIVATE type listeners and not any other types', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + //add 2 different listeners for ACTIVATE + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only ACTIVATE type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).not.toHaveBeenCalled(); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove DECISION type listeners and not any other types', () => { + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for DECISION + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only DECISION type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove LOG_EVENT type listeners and not any other types', () => { + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for LOG_EVENT + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only LOG_EVENT type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove OPTIMIZELY_CONFIG_UPDATE type listeners and not any other types', () => { + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only OPTIMIZELY_CONFIG_UPDATE type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove TRACK type listeners and not any other types', () => { + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + // add 2 different listeners for TRACK + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + // remove only TRACK type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + }); +}); + +describe('sendNotifications', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + it('should call the listener callback with exact arguments', () => { + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // listener object data for each type + const activateData = { + experiment: {}, + userId: '', + attributes: {}, + variation: {}, + logEvent: {}, + }; + const decisionData = { + type: '', + userId: 'use1', + attributes: {}, + decisionInfo: {}, + }; + const logEventData = { + url: '', + httpVerb: '', + params: {}, + }; + const configUpdateData = {}; + const trackData = { + eventKey: '', + userId: '', + attributes: {}, + eventTags: {}, + }; + // add listeners + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, activateData as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, decisionData as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, logEventData as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + (configUpdateData as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, trackData as TrackListenerPayload); + + expect(activateCallbackSpy1).toHaveBeenCalledWith(activateData); + expect(decisionCallbackSpy1).toHaveBeenCalledWith(decisionData); + expect(logEventCallbackSpy1).toHaveBeenCalledWith(logEventData); + expect(configUpdateCallbackSpy1).toHaveBeenCalledWith(configUpdateData); + expect(trackCallbackSpy1).toHaveBeenCalledWith(trackData); + }); +}); diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index bb5370ef4..36ffbe89a 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -322,9 +322,6 @@ describe('getLayerId', () => { }); it('should throw error for invalid experiment key in getLayerId', function() { - // expect(() => projectConfig.getLayerId(configObj, 'invalidExperimentKey')).toThrowError( - // sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey') - // ); expect(() => projectConfig.getLayerId(configObj, 'invalidExperimentKey')).toThrowError( expect.objectContaining({ baseMessage: INVALID_EXPERIMENT_ID, diff --git a/lib/utils/attributes_validator/index.spec.ts b/lib/utils/attributes_validator/index.spec.ts new file mode 100644 index 000000000..645fa2113 --- /dev/null +++ b/lib/utils/attributes_validator/index.spec.ts @@ -0,0 +1,115 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import * as attributesValidator from './'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it('should validate the given attributes if attributes is an object', () => { + expect(attributesValidator.validate({ testAttribute: 'testValue' })).toBe(true); + }); + + it('should throw an error if attributes is an array', () => { + const attributesArray = ['notGonnaWork']; + + expect(() => attributesValidator.validate(attributesArray)).toThrow(OptimizelyError); + + try { + attributesValidator.validate(attributesArray); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } + }); + + it('should throw an error if attributes is null', () => { + expect(() => attributesValidator.validate(null)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(null); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } + }); + + it('should throw an error if attributes is a function', () => { + function invalidInput() { + console.log('This is an invalid input!'); + } + + expect(() => attributesValidator.validate(invalidInput)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(invalidInput); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } + }); + + it('should throw an error if attributes contains a key with an undefined value', () => { + const attributeKey = 'testAttribute'; + const attributes: Record<string, unknown> = {}; + attributes[attributeKey] = undefined; + + expect(() => attributesValidator.validate(attributes)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(attributes); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(UNDEFINED_ATTRIBUTE); + expect(err.params).toEqual([attributeKey]); + } + }); +}); + +describe('isAttributeValid', () => { + it('isAttributeValid returns true for valid values', () => { + const userAttributes: Record<string, unknown> = { + browser_type: 'Chrome', + is_firefox: false, + num_users: 10, + pi_value: 3.14, + '': 'javascript', + }; + + Object.keys(userAttributes).forEach(key => { + const value = userAttributes[key]; + + expect(attributesValidator.isAttributeValid(key, value)).toBe(true); + }); + }); + it('isAttributeValid returns false for invalid values', () => { + const userAttributes: Record<string, unknown> = { + null: null, + objects: { a: 'b' }, + array: [1, 2, 3], + infinity: Infinity, + negativeInfinity: -Infinity, + NaN: NaN, + }; + + Object.keys(userAttributes).forEach(key => { + const value = userAttributes[key]; + + expect(attributesValidator.isAttributeValid(key, value)).toBe(false); + }); + }); +}); diff --git a/lib/utils/config_validator/index.spec.ts b/lib/utils/config_validator/index.spec.ts new file mode 100644 index 000000000..c8496ecc4 --- /dev/null +++ b/lib/utils/config_validator/index.spec.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import configValidator from './'; +import testData from '../../tests/test_data'; +import { INVALID_DATAFILE_MALFORMED, INVALID_DATAFILE_VERSION, NO_DATAFILE_SPECIFIED } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it('should complain if datafile is not provided', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => configValidator.validateDatafile()).toThrow(OptimizelyError); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + configValidator.validateDatafile(); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(NO_DATAFILE_SPECIFIED); + } + }); + + it('should complain if datafile is malformed', () => { + expect(() => configValidator.validateDatafile('abc')).toThrow( OptimizelyError); + + try { + configValidator.validateDatafile('abc'); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_DATAFILE_MALFORMED); + } + }); + + it('should complain if datafile version is not supported', () => { + expect(() => configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())).toThrow(OptimizelyError)); + + try { + configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_DATAFILE_VERSION); + expect(err.params).toEqual(['5']); + } + }); + + it('should not complain if datafile is valid', () => { + expect(() => configValidator.validateDatafile(JSON.stringify(testData.getTestProjectConfig())).not.toThrowError()); + }); +}); diff --git a/lib/utils/event_tag_utils/index.spec.ts b/lib/utils/event_tag_utils/index.spec.ts new file mode 100644 index 000000000..a1208b601 --- /dev/null +++ b/lib/utils/event_tag_utils/index.spec.ts @@ -0,0 +1,88 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import * as eventTagUtils from './'; +import { + FAILED_TO_PARSE_REVENUE, + PARSED_REVENUE_VALUE, + PARSED_NUMERIC_VALUE, + FAILED_TO_PARSE_VALUE, +} from 'log_message'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { LoggerFacade } from '../../logging/logger'; + +describe('getRevenueValue', () => { + let logger: LoggerFacade; + + beforeEach(() => { + logger = getMockLogger(); + }); + + it('should return the parseed integer for a valid revenue value', () => { + let parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: '1337' }, logger); + + expect(parsedRevenueValue).toBe(1337); + expect(logger.info).toHaveBeenCalledWith(PARSED_REVENUE_VALUE, 1337); + + parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: '13.37' }, logger); + + expect(parsedRevenueValue).toBe(13); + }); + + it('should return null and log a message for invalid value', () => { + const parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: 'invalid' }, logger); + + expect(parsedRevenueValue).toBe(null); + expect(logger.info).toHaveBeenCalledWith(FAILED_TO_PARSE_REVENUE, 'invalid'); + }); + + it('should return null if the revenue value is not present in the event tags', () => { + const parsedRevenueValue = eventTagUtils.getRevenueValue({ not_revenue: '1337' }, logger); + + expect(parsedRevenueValue).toBe(null); + }); +}); + +describe('getEventValue', () => { + let logger: LoggerFacade; + + beforeEach(() => { + logger = getMockLogger(); + }); + + it('should return the parsed integer for a valid numeric value', () => { + let parsedEventValue = eventTagUtils.getEventValue({ value: '1337' }, logger); + + expect(parsedEventValue).toBe(1337); + expect(logger.info).toHaveBeenCalledWith(PARSED_NUMERIC_VALUE, 1337); + + parsedEventValue = eventTagUtils.getEventValue({ value: '13.37' }, logger); + expect(parsedEventValue).toBe(13.37); + }); + + it('should return null and log a message for invalid value', () => { + const parsedNumericValue = eventTagUtils.getEventValue({ value: 'invalid' }, logger); + + expect(parsedNumericValue).toBe(null); + expect(logger.info).toHaveBeenCalledWith(FAILED_TO_PARSE_VALUE, 'invalid'); + }); + + it('should return null if the value is not present in the event tags', () => { + const parsedNumericValue = eventTagUtils.getEventValue({ not_value: '13.37' }, logger); + + expect(parsedNumericValue).toBe(null); + }); +}) diff --git a/lib/utils/event_tags_validator/index.spec.ts b/lib/utils/event_tags_validator/index.spec.ts new file mode 100644 index 000000000..1b372ff0a --- /dev/null +++ b/lib/utils/event_tags_validator/index.spec.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { validate } from '.'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { INVALID_EVENT_TAGS } from 'error_message'; + +describe('validate', () => { + it('should validate the given event tags if event tag is an object', () => { + expect(validate({ testAttribute: 'testValue' })).toBe(true); + }); + + it('should throw an error if event tags is an array', () => { + const eventTagsArray = ['notGonnaWork']; + + expect(() => validate(eventTagsArray)).toThrow(OptimizelyError) + + try { + validate(eventTagsArray); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); + + it('should throw an error if event tags is null', () => { + expect(() => validate(null)).toThrow(OptimizelyError); + + try { + validate(null); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); + + it('should throw an error if event tags is a function', () => { + function invalidInput() { + console.log('This is an invalid input!'); + } + expect(() => validate(invalidInput)).toThrow(OptimizelyError); + + try { + validate(invalidInput); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); +}); diff --git a/lib/utils/json_schema_validator/index.spec.ts b/lib/utils/json_schema_validator/index.spec.ts new file mode 100644 index 000000000..20af5b51d --- /dev/null +++ b/lib/utils/json_schema_validator/index.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from '.'; +import testData from '../../tests/test_data'; +import { NO_JSON_PROVIDED, INVALID_DATAFILE } from 'error_message'; + +describe('validate', () => { + it('should throw an error if the object is not valid', () => { + expect(() => validate({})).toThrow(); + + try { + validate({}); + } catch (err) { + expect(err.baseMessage).toBe(INVALID_DATAFILE); + } + }); + + it('should throw an error if no json object is passed in', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => validate()).toThrow(); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + validate(); + } catch (err) { + expect(err.baseMessage).toBe(NO_JSON_PROVIDED); + } + }); + + it('should validate specified Optimizely datafile', () => { + expect(validate(testData.getTestProjectConfig())).toBe(true); + }); +}); diff --git a/lib/utils/semantic_version/index.spec.ts b/lib/utils/semantic_version/index.spec.ts new file mode 100644 index 000000000..15dbbdbb9 --- /dev/null +++ b/lib/utils/semantic_version/index.spec.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import * as semanticVersion from '.'; + +describe('compareVersion', () => { + it('should return 0 if user version and target version are equal', () => { + const versions = [ + ['2.0.1', '2.0.1'], + ['2.9.9-beta', '2.9.9-beta'], + ['2.1', '2.1.0'], + ['2', '2.12'], + ['2.9', '2.9.1'], + ['2.9+beta', '2.9+beta'], + ['2.9.9+beta', '2.9.9+beta'], + ['2.9.9+beta-alpha', '2.9.9+beta-alpha'], + ['2.2.3', '2.2.3+beta'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(0); + }) + }); + + it('should return 1 when user version is greater than target version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['2.0', '3.0.1'], + ['2.0.0', '2.1'], + ['2.1.2-beta', '2.1.2-release'], + ['2.1.3-beta1', '2.1.3-beta2'], + ['2.9.9-beta', '2.9.9'], + ['2.9.9+beta', '2.9.9'], + ['2.0.0', '2.1'], + ['3.7.0-prerelease+build', '3.7.0-prerelease+rc'], + ['2.2.3-beta-beta1', '2.2.3-beta-beta2'], + ['2.2.3-beta+beta1', '2.2.3-beta+beta2'], + ['2.2.3+beta2-beta1', '2.2.3+beta3-beta2'], + ['2.2.3+beta', '2.2.3'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(1); + }) + }); + + it('should return -1 when user version is less than target version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['3.0', '2.0.1'], + ['2.3', '2.0.1'], + ['2.3.5', '2.3.1'], + ['2.9.8', '2.9'], + ['3.1', '3'], + ['2.1.2-release', '2.1.2-beta'], + ['2.9.9+beta', '2.9.9-beta'], + ['3.7.0+build3.7.0-prerelease+build', '3.7.0-prerelease'], + ['2.1.3-beta-beta2', '2.1.3-beta'], + ['2.1.3-beta1+beta3', '2.1.3-beta1+beta2'], + ['2.1.3', '2.1.3-beta'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(-1); + }) + }); + + it('should return null when user version is invalid', () => { + const versions = [ + '-', + '.', + '..', + '+', + '+test', + ' ', + '2 .3. 0', + '2.', + '.2.2', + '3.7.2.2', + '3.x', + ',', + '+build-prerelease', + '2..2', + ]; + const targetVersion = '2.1.0'; + + versions.forEach((userVersion) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(null); + }) + }); +}); diff --git a/lib/utils/string_value_validator/index.spec.ts b/lib/utils/string_value_validator/index.spec.ts new file mode 100644 index 000000000..a9c7f6a91 --- /dev/null +++ b/lib/utils/string_value_validator/index.spec.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from './'; + +describe('validate', () => { + it('should validate the given value is valid string', () => { + expect(validate('validStringValue')).toBe(true); + }); + + it('should return false if given value is invalid string', () => { + expect(validate(null)).toBe(false); + expect(validate(undefined)).toBe(false); + expect(validate('')).toBe(false); + expect(validate(5)).toBe(false); + expect(validate(true)).toBe(false); + expect(validate([])).toBe(false); + }); +}); diff --git a/lib/utils/user_profile_service_validator/index.spec.ts b/lib/utils/user_profile_service_validator/index.spec.ts new file mode 100644 index 000000000..98a47ef60 --- /dev/null +++ b/lib/utils/user_profile_service_validator/index.spec.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from './'; +import { INVALID_USER_PROFILE_SERVICE } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it("should throw if the instance does not provide a 'lookup' function", () => { + const missingLookupFunction = { + save: function() {}, + }; + + expect(() => validate(missingLookupFunction)).toThrowError(OptimizelyError); + + try { + validate(missingLookupFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } + }); + + it("should throw if 'lookup' is not a function", () => { + const lookupNotFunction = { + save: function() {}, + lookup: 'notGonnaWork', + }; + + expect(() => validate(lookupNotFunction)).toThrowError(OptimizelyError); + + try { + validate(lookupNotFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } + }); + + it("should throw if the instance does not provide a 'save' function", () => { + const missingSaveFunction = { + lookup: function() {}, + }; + + expect(() => validate(missingSaveFunction)).toThrowError(OptimizelyError); + + try { + validate(missingSaveFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } + }); + + it("should throw if 'save' is not a function", () => { + const saveNotFunction = { + lookup: function() {}, + save: 'notGonnaWork', + }; + + expect(() => validate(saveNotFunction)).toThrowError(OptimizelyError); + + try { + validate(saveNotFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } + }); + + it('should return true if the instance is valid', () => { + const validInstance = { + save: function() {}, + lookup: function() {}, + }; + + expect(validate(validInstance)).toBe(true); + }); +}); From 721185f124630ccb603f6362978de49927a74229 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 3 Apr 2025 23:19:47 +0600 Subject: [PATCH 138/200] [FSSDK-11128] update decision service and impression event for CMAB (#1021) --- lib/client_factory.ts | 24 +- lib/core/bucketer/index.ts | 3 +- .../cmab/cmab_service.spec.ts | 63 +- .../decision_service/cmab/cmab_service.ts | 22 +- lib/core/decision_service/index.spec.ts | 413 ++++++++--- lib/core/decision_service/index.tests.js | 38 +- lib/core/decision_service/index.ts | 642 ++++++++++++------ lib/entrypoint.universal.test-d.ts | 3 +- .../event_builder/log_event.ts | 4 +- .../event_builder/user_event.tests.js | 2 + .../event_builder/user_event.ts | 3 + lib/index.browser.ts | 6 +- lib/index.node.ts | 2 + lib/index.react_native.ts | 2 + lib/index.universal.ts | 8 +- lib/message/error_message.ts | 1 + lib/optimizely/index.spec.ts | 118 ++++ lib/optimizely/index.tests.js | 21 + lib/optimizely/index.ts | 120 +++- lib/shared_types.ts | 3 +- lib/tests/decision_test_datafile.ts | 91 ++- lib/utils/enums/index.ts | 4 +- lib/utils/promise/operation_value.ts | 50 ++ lib/utils/type.ts | 3 + 24 files changed, 1235 insertions(+), 411 deletions(-) create mode 100644 lib/utils/promise/operation_value.ts diff --git a/lib/client_factory.ts b/lib/client_factory.ts index 898df5575..187334cc4 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -24,11 +24,18 @@ import { extractConfigManager } from "./project_config/config_manager_factory"; import { extractEventProcessor } from "./event_processor/event_processor_factory"; import { extractOdpManager } from "./odp/odp_manager_factory"; import { extractVuidManager } from "./vuid/vuid_manager_factory"; - -import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums"; +import { RequestHandler } from "./utils/http_request_handler/http"; +import { CLIENT_VERSION, DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums"; import Optimizely from "./optimizely"; +import { DefaultCmabClient } from "./core/decision_service/cmab/cmab_client"; +import { CmabCacheValue, DefaultCmabService } from "./core/decision_service/cmab/cmab_service"; +import { InMemoryLruCache } from "./utils/cache/in_memory_lru_cache"; + +export type OptimizelyFactoryConfig = Config & { + requestHandler: RequestHandler; +} -export const getOptimizelyInstance = (config: Config): Client | null => { +export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client | null => { let logger: Maybe<LoggerFacade>; try { @@ -43,6 +50,7 @@ export const getOptimizelyInstance = (config: Config): Client | null => { userProfileService, defaultDecideOptions, disposable, + requestHandler, } = config; const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; @@ -52,7 +60,17 @@ export const getOptimizelyInstance = (config: Config): Client | null => { const odpManager = config.odpManager ? extractOdpManager(config.odpManager) : undefined; const vuidManager = config.vuidManager ? extractVuidManager(config.vuidManager) : undefined; + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const cmabService = new DefaultCmabService({ + cmabClient, + cmabCache: new InMemoryLruCache<CmabCacheValue>(DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT), + }); + const optimizelyOptions = { + cmabService, clientEngine: clientEngine || JAVASCRIPT_CLIENT_ENGINE, clientVersion: clientVersion || CLIENT_VERSION, jsonSchemaValidator, diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index b2455b95a..686f49abd 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -27,6 +27,7 @@ import { import { INVALID_GROUP_ID } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; import { generateBucketValue } from './bucket_value_generator'; +import { DecisionReason } from '../decision_service'; export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.'; export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.'; @@ -52,7 +53,7 @@ const RANDOM_POLICY = 'random'; * null if user is not bucketed into any experiment and the decide reasons. */ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse<string | null> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; // Check if user is in a random group; if so, check if user is bucketed into a specific experiment const experiment = bucketerParams.experimentIdMap[bucketerParams.experimentId]; const groupId = experiment['groupId']; diff --git a/lib/core/decision_service/cmab/cmab_service.spec.ts b/lib/core/decision_service/cmab/cmab_service.spec.ts index 2e571932d..dce84f6e1 100644 --- a/lib/core/decision_service/cmab/cmab_service.spec.ts +++ b/lib/core/decision_service/cmab/cmab_service.spec.ts @@ -68,7 +68,7 @@ describe('DefaultCmabService', () => { }); const ruleId = '1234'; - const variation = await cmabService.getDecision(projectConfig, userContext, ruleId, []); + const variation = await cmabService.getDecision(projectConfig, userContext, ruleId, {}); expect(variation.variationId).toEqual('123'); expect(uuidValidate(variation.cmabUuid)).toBe(true); @@ -101,8 +101,8 @@ describe('DefaultCmabService', () => { gender: 'male' }); - await cmabService.getDecision(projectConfig, userContext, '1234', []); - await cmabService.getDecision(projectConfig, userContext, '5678', []); + await cmabService.getDecision(projectConfig, userContext, '1234', {}); + await cmabService.getDecision(projectConfig, userContext, '5678', {}); expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); expect(mockCmabClient.fetchDecision.mock.calls[0][2]).toEqual({ @@ -136,7 +136,7 @@ describe('DefaultCmabService', () => { gender: 'male' }); - const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', []); + const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', {}); const userContext12 = mockUserContext('user123', { country: 'US', @@ -145,7 +145,7 @@ describe('DefaultCmabService', () => { gender: 'female' }); - const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', []); + const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', {}); expect(variation11.variationId).toEqual('123'); expect(variation12.variationId).toEqual('123'); expect(variation11.cmabUuid).toEqual(variation12.cmabUuid); @@ -157,14 +157,14 @@ describe('DefaultCmabService', () => { age: '30', }); - const variation21 = await cmabService.getDecision(projectConfig, userContext21, '5678', []); + const variation21 = await cmabService.getDecision(projectConfig, userContext21, '5678', {}); const userContext22 = mockUserContext('user456', { country: 'BD', age: '35', }); - const variation22 = await cmabService.getDecision(projectConfig, userContext22, '5678', []); + const variation22 = await cmabService.getDecision(projectConfig, userContext22, '5678', {}); expect(variation21.variationId).toEqual('456'); expect(variation22.variationId).toEqual('456'); expect(variation21.cmabUuid).toEqual(variation22.cmabUuid); @@ -192,7 +192,7 @@ describe('DefaultCmabService', () => { gender: 'male' }); - const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', []); + const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', {}); const userContext12 = mockUserContext('user123', { gender: 'female', @@ -201,7 +201,7 @@ describe('DefaultCmabService', () => { age: '25', }); - const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', []); + const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', {}); expect(variation11.variationId).toEqual('123'); expect(variation12.variationId).toEqual('123'); expect(variation11.cmabUuid).toEqual(variation12.cmabUuid); @@ -227,9 +227,9 @@ describe('DefaultCmabService', () => { age: '25', }); - const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); - const variation2 = await cmabService.getDecision(projectConfig, userContext, '5678', []); + const variation2 = await cmabService.getDecision(projectConfig, userContext, '5678', {}); expect(variation1.variationId).toEqual('123'); expect(variation2.variationId).toEqual('456'); @@ -260,9 +260,9 @@ describe('DefaultCmabService', () => { age: '25', }); - const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []); + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {}); - const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); expect(variation1.variationId).toEqual('123'); expect(variation2.variationId).toEqual('456'); expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); @@ -289,7 +289,7 @@ describe('DefaultCmabService', () => { gender: 'male' }); - const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []); + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {}); const userContext2 = mockUserContext('user123', { country: 'US', @@ -298,7 +298,7 @@ describe('DefaultCmabService', () => { gender: 'male' }); - const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); expect(variation1.variationId).toEqual('123'); expect(variation2.variationId).toEqual('456'); expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); @@ -325,13 +325,13 @@ describe('DefaultCmabService', () => { gender: 'male' }); - const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); - const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', [ - OptimizelyDecideOption.IGNORE_CMAB_CACHE, - ]); + const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', { + [OptimizelyDecideOption.IGNORE_CMAB_CACHE]: true, + }); - const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); expect(variation1.variationId).toEqual('123'); expect(variation2.variationId).toEqual('456'); @@ -367,18 +367,19 @@ describe('DefaultCmabService', () => { age: '50' }); - const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []); + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {}); expect(variation1.variationId).toEqual('123'); - const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); expect(variation2.variationId).toEqual('456'); - const variation3 = await cmabService.getDecision(projectConfig, userContext1, '1234', [ - OptimizelyDecideOption.RESET_CMAB_CACHE, - ]); + const variation3 = await cmabService.getDecision(projectConfig, userContext1, '1234', { + [OptimizelyDecideOption.RESET_CMAB_CACHE]: true, + }); + expect(variation3.variationId).toEqual('789'); - const variation4 = await cmabService.getDecision(projectConfig, userContext2, '1234', []); + const variation4 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); expect(variation4.variationId).toEqual('101112'); }); @@ -401,13 +402,13 @@ describe('DefaultCmabService', () => { gender: 'male' }); - const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); - const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', [ - OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE, - ]); + const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', { + [OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]: true, + }); - const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', []); + const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); expect(variation1.variationId).toEqual('123'); expect(variation2.variationId).toEqual('456'); diff --git a/lib/core/decision_service/cmab/cmab_service.ts b/lib/core/decision_service/cmab/cmab_service.ts index 2eaffd4fd..b4f958fbf 100644 --- a/lib/core/decision_service/cmab/cmab_service.ts +++ b/lib/core/decision_service/cmab/cmab_service.ts @@ -15,14 +15,14 @@ */ import { LoggerFacade } from "../../../logging/logger"; -import OptimizelyUserContext from "../../../optimizely_user_context" +import { IOptimizelyUserContext } from "../../../optimizely_user_context"; import { ProjectConfig } from "../../../project_config/project_config" import { OptimizelyDecideOption, UserAttributes } from "../../../shared_types" import { Cache } from "../../../utils/cache/cache"; import { CmabClient } from "./cmab_client"; import { v4 as uuidV4 } from 'uuid'; import murmurhash from "murmurhash"; -import { a } from "vitest/dist/chunks/suite.CcK46U-P"; +import { DecideOptionsMap } from ".."; export type CmabDecision = { variationId: string, @@ -32,16 +32,16 @@ export type CmabDecision = { export interface CmabService { /** * Get variation id for the user - * @param {OptimizelyUserContext} userContext + * @param {IOptimizelyUserContext} userContext * @param {string} ruleId * @param {OptimizelyDecideOption[]} options * @return {Promise<CmabDecision>} */ getDecision( projectConfig: ProjectConfig, - userContext: OptimizelyUserContext, + userContext: IOptimizelyUserContext, ruleId: string, - options: OptimizelyDecideOption[] + options: DecideOptionsMap, ): Promise<CmabDecision> } @@ -70,23 +70,23 @@ export class DefaultCmabService implements CmabService { async getDecision( projectConfig: ProjectConfig, - userContext: OptimizelyUserContext, + userContext: IOptimizelyUserContext, ruleId: string, - options: OptimizelyDecideOption[] + options: DecideOptionsMap, ): Promise<CmabDecision> { const filteredAttributes = this.filterAttributes(projectConfig, userContext, ruleId); - if (options.includes(OptimizelyDecideOption.IGNORE_CMAB_CACHE)) { + if (options[OptimizelyDecideOption.IGNORE_CMAB_CACHE]) { return this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes); } - if (options.includes(OptimizelyDecideOption.RESET_CMAB_CACHE)) { + if (options[OptimizelyDecideOption.RESET_CMAB_CACHE]) { this.cmabCache.clear(); } const cacheKey = this.getCacheKey(userContext.getUserId(), ruleId); - if (options.includes(OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE)) { + if (options[OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]) { this.cmabCache.remove(cacheKey); } @@ -125,7 +125,7 @@ export class DefaultCmabService implements CmabService { private filterAttributes( projectConfig: ProjectConfig, - userContext: OptimizelyUserContext, + userContext: IOptimizelyUserContext, ruleId: string ): UserAttributes { const filteredAttributes: UserAttributes = {}; diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts index cbfbaf7be..f3459ef0e 100644 --- a/lib/core/decision_service/index.spec.ts +++ b/lib/core/decision_service/index.spec.ts @@ -14,15 +14,16 @@ * limitations under the License. */ import { describe, it, expect, vi, MockInstance, beforeEach } from 'vitest'; -import { DecisionService } from '.'; +import { CMAB_FETCH_FAILED, DecisionService } from '.'; import { getMockLogger } from '../../tests/mock/mock_logger'; import OptimizelyUserContext from '../../optimizely_user_context'; import { bucket } from '../bucketer'; import { getTestProjectConfig, getTestProjectConfigWithFeatures } from '../../tests/test_data'; import { createProjectConfig, ProjectConfig } from '../../project_config/project_config'; -import { BucketerParams, Experiment, UserProfile } from '../../shared_types'; +import { BucketerParams, Experiment, OptimizelyDecideOption, UserProfile } from '../../shared_types'; import { CONTROL_ATTRIBUTES, DECISION_SOURCES } from '../../utils/enums'; import { getDecisionTestDatafile } from '../../tests/decision_test_datafile'; +import { Value } from '../../utils/promise/operation_value'; import { USER_HAS_NO_FORCED_VARIATION, @@ -49,15 +50,20 @@ import { } from '../decision_service/index'; import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from 'error_message'; -import exp from 'constants'; type MockLogger = ReturnType<typeof getMockLogger>; +type MockFnType = ReturnType<typeof vi.fn>; + type MockUserProfileService = { - lookup: ReturnType<typeof vi.fn>; - save: ReturnType<typeof vi.fn>; + lookup: MockFnType; + save: MockFnType; }; +type MockCmabService = { + getDecision: MockFnType; +} + type DecisionServiceInstanceOpt = { logger?: boolean; userProfileService?: boolean; @@ -66,6 +72,7 @@ type DecisionServiceInstanceOpt = { type DecisionServiceInstance = { logger?: MockLogger; userProfileService?: MockUserProfileService; + cmabService: MockCmabService; decisionService: DecisionService; } @@ -76,16 +83,22 @@ const getDecisionService = (opt: DecisionServiceInstanceOpt = {}): DecisionServi save: vi.fn(), } : undefined; + const cmabService = { + getDecision: vi.fn(), + }; + const decisionService = new DecisionService({ logger, userProfileService, UNSTABLE_conditionEvaluators: {}, + cmabService, }); return { logger, userProfileService, decisionService, + cmabService, }; }; @@ -764,7 +777,7 @@ describe('DecisionService', () => { }); }); - describe('getVariationForFeature', () => { + describe('getVariationForFeature - sync', () => { beforeEach(() => { mockBucket.mockReset(); }); @@ -774,22 +787,23 @@ describe('DecisionService', () => { const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') .mockImplementation(( + op, config, experiment: any, user, - shouldIgnoreUPS, - userProfileTracker + decideOptions, + userProfileTracker: any, ) => { if (experiment.key === 'exp_2') { - return { - result: 'variation_2', + return Value.of('sync', { + result: { variationKey: 'variation_2' }, reasons: [], - }; + }); } - return { - result: null, + return Value.of('sync', { + result: {}, reasons: [], - } + }); }); const config = createProjectConfig(getDecisionTestDatafile()); @@ -813,9 +827,9 @@ describe('DecisionService', () => { expect(resolveVariationSpy).toHaveBeenCalledTimes(2); expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, - config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, - config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); }); it('should return the variation forced for an experiment in the userContext if available', () => { @@ -823,22 +837,23 @@ describe('DecisionService', () => { const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') .mockImplementation(( + op, config, experiment: any, user, - shouldIgnoreUPS, - userProfileTracker + decideOptions, + userProfileTracker: any, ) => { if (experiment.key === 'exp_2') { - return { - result: 'variation_2', + return Value.of('sync', { + result: { varationKey: 'variation_2' }, reasons: [], - }; + }); } - return { - result: null, + return Value.of('sync', { + result: {}, reasons: [], - } + }); }); const config = createProjectConfig(getDecisionTestDatafile()); @@ -871,10 +886,11 @@ describe('DecisionService', () => { const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') .mockImplementation(( + op, config, experiment: any, user, - shouldIgnoreUPS, + decideOptions, userProfileTracker: any, ) => { if (experiment.key === 'exp_2') { @@ -885,15 +901,15 @@ describe('DecisionService', () => { }; userProfileTracker.isProfileUpdated = true; - return { - result: 'variation_2', + return Value.of('sync', { + result: { variationKey: 'variation_2' }, reasons: [], - }; + }); } - return { - result: null, + return Value.of('sync', { + result: {}, reasons: [], - } + }); }); const config = createProjectConfig(getDecisionTestDatafile()); @@ -958,10 +974,10 @@ describe('DecisionService', () => { const { decisionService } = getDecisionService(); const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') - .mockReturnValue({ - result: null, + .mockReturnValue(Value.of('sync', { + result: {}, reasons: [], - }); + })); const config = createProjectConfig(getDecisionTestDatafile()); @@ -998,11 +1014,11 @@ describe('DecisionService', () => { expect(resolveVariationSpy).toHaveBeenCalledTimes(3); expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, - config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, - config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, - config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); expect(mockBucket).toHaveBeenCalledTimes(1); verifyBucketCall(0, config, config.experimentIdMap['3002'], user); @@ -1012,10 +1028,10 @@ describe('DecisionService', () => { const { decisionService } = getDecisionService(); const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') - .mockReturnValue({ - result: null, + .mockReturnValue(Value.of('sync', { + result: {}, reasons: [], - }); + })); const config = createProjectConfig(getDecisionTestDatafile()); @@ -1053,11 +1069,11 @@ describe('DecisionService', () => { expect(resolveVariationSpy).toHaveBeenCalledTimes(3); expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, - config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, - config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, - config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); expect(mockBucket).toHaveBeenCalledTimes(1); verifyBucketCall(0, config, config.experimentIdMap['3002'], user, true); @@ -1068,10 +1084,10 @@ describe('DecisionService', () => { const { decisionService } = getDecisionService(); const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') - .mockReturnValue({ - result: null, + .mockReturnValue(Value.of('sync', { + result: {}, reasons: [], - }); + })); const config = createProjectConfig(getDecisionTestDatafile()); @@ -1108,11 +1124,11 @@ describe('DecisionService', () => { expect(resolveVariationSpy).toHaveBeenCalledTimes(3); expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, - config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, - config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, - config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); expect(mockBucket).toHaveBeenCalledTimes(2); verifyBucketCall(0, config, config.experimentIdMap['3002'], user); @@ -1127,16 +1143,17 @@ describe('DecisionService', () => { const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') .mockImplementation(( + op, config, experiment: any, user, - shouldIgnoreUPS, - userProfileTracker + decideOptions, + userProfileTracker: any, ) => { - return { - result: null, + return Value.of('sync', { + result: {}, reasons: [], - } + }); }); const config = createProjectConfig(getDecisionTestDatafile()); @@ -1166,10 +1183,10 @@ describe('DecisionService', () => { const { decisionService } = getDecisionService(); const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') - .mockReturnValue({ - result: null, + .mockReturnValue(Value.of('sync', { + result: {}, reasons: [], - }); + })); const config = createProjectConfig(getDecisionTestDatafile()); @@ -1206,11 +1223,11 @@ describe('DecisionService', () => { expect(resolveVariationSpy).toHaveBeenCalledTimes(3); expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, - config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, - config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, - config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); expect(mockBucket).toHaveBeenCalledTimes(1); verifyBucketCall(0, config, config.experimentIdMap['default-rollout-id'], user); @@ -1220,10 +1237,10 @@ describe('DecisionService', () => { const { decisionService } = getDecisionService(); const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') - .mockReturnValue({ - result: null, + .mockReturnValue(Value.of('sync', { + result: {}, reasons: [], - }); + })); const config = createProjectConfig(getDecisionTestDatafile()); const rolloutId = config.featureKeyMap['flag_1'].rolloutId; @@ -1248,16 +1265,236 @@ describe('DecisionService', () => { expect(resolveVariationSpy).toHaveBeenCalledTimes(3); expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, - config, config.experimentKeyMap['exp_1'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, - config, config.experimentKeyMap['exp_2'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, - config, config.experimentKeyMap['exp_3'], user, false, expect.anything()); + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); expect(mockBucket).toHaveBeenCalledTimes(0); }); }); + describe('resolveVariationForFeatureList - async', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return variation from the first experiment for which a variation is available', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, // should satisfy audience condition for all experiments + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'exp_2') { + return { + result: '5002', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should get decision from the cmab service if the experiment is a cmab experiment', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + }); + + it('should pass the correct DecideOptionMap to cmabService', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, { + [OptimizelyDecideOption.RESET_CMAB_CACHE]: true, + [OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]: true, + }).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + { + [OptimizelyDecideOption.RESET_CMAB_CACHE]: true, + [OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]: true, + }, + ); + }); + + it('should return error if cmab getDecision fails', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + cmabService.getDecision.mockRejectedValue(new Error('I am an error')); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.error).toBe(true); + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_3'], + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variation.reasons).toContainEqual( + [CMAB_FETCH_FAILED, 'exp_3'], + ); + + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + }); + }); + + describe('resolveVariationForFeatureList - sync', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should skip cmab experiments', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, // should satisfy audience condition for all experiments and targeted delivery + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'delivery_1') { + return { + result: '5004', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user, {}).get(); + + const variation = value[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_1'], + variation: config.variationIdMap['5004'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(mockBucket).toHaveBeenCalledTimes(3); + verifyBucketCall(0, config, config.experimentKeyMap['exp_1'], user); + verifyBucketCall(1, config, config.experimentKeyMap['exp_2'], user); + verifyBucketCall(2, config, config.experimentKeyMap['delivery_1'], user); + + expect(cmabService.getDecision).not.toHaveBeenCalled(); + }); + }); + describe('getVariationsForFeatureList', () => { beforeEach(() => { mockBucket.mockReset(); @@ -1268,27 +1505,28 @@ describe('DecisionService', () => { const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') .mockImplementation(( + op, config, experiment: any, user, - shouldIgnoreUPS, - userProfileTracker + decideOptions, + userProfileTracker: any, ) => { if (experiment.key === 'exp_2') { - return { - result: 'variation_2', + return Value.of('sync', { + result: { variationKey: 'variation_2' }, reasons: [], - }; + }); } else if (experiment.key === 'exp_4') { - return { - result: 'variation_flag_2', + return Value.of('sync', { + result: { variationKey: 'variation_flag_2' }, reasons: [], - }; + }); } - return { - result: null, + return Value.of('sync', { + result: {}, reasons: [], - } + }); }); const config = createProjectConfig(getDecisionTestDatafile()); @@ -1340,10 +1578,11 @@ describe('DecisionService', () => { const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') .mockImplementation(( + op, config, experiment: any, user, - shouldIgnoreUPS, + decideOptions, userProfileTracker: any, ) => { if (experiment.key === 'exp_2') { @@ -1352,25 +1591,25 @@ describe('DecisionService', () => { }; userProfileTracker.isProfileUpdated = true; - return { - result: 'variation_2', + return Value.of('sync', { + result: { variationKey: 'variation_2' }, reasons: [], - }; + }); } else if (experiment.key === 'exp_4') { userProfileTracker.userProfile[experiment.id] = { variation_id: '5100', }; userProfileTracker.isProfileUpdated = true; - return { - result: 'variation_flag_2', + return Value.of('sync', { + result: { variationKey: 'variation_flag_2' }, reasons: [], - }; + }); } - return { - result: null, + return Value.of('sync', { + result: {}, reasons: [], - } + }); }); const config = createProjectConfig(getDecisionTestDatafile()); diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 8dd68aa88..98f9dde70 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -34,6 +34,7 @@ import AudienceEvaluator from '../audience_evaluator'; import eventDispatcher from '../../event_processor/event_dispatcher/default_dispatcher.browser'; import * as jsonSchemaValidator from '../../utils/json_schema_validator'; import { getMockProjectConfigManager } from '../../tests/mock/mock_project_config_manager'; +import { Value } from '../../utils/promise/operation_value'; import { getTestProjectConfig, @@ -1207,10 +1208,10 @@ describe('lib/core/decision_service', function() { var sandbox; var mockLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); var fakeDecisionResponseWithArgs; - var fakeDecisionResponse = { - result: null, + var fakeDecisionResponse = Value.of('sync', { + result: {}, reasons: [], - }; + }); var user; beforeEach(function() { configObj = projectConfig.createProjectConfig(cloneDeep(testDataWithFeatures)); @@ -1247,19 +1248,20 @@ describe('lib/core/decision_service', function() { test_attribute: 'test_value', }, }); - fakeDecisionResponseWithArgs = { - result: 'variation', + fakeDecisionResponseWithArgs = Value.of('sync', { + result: { variationKey: 'variation' }, reasons: [], - }; + }); experiment = configObj.experimentIdMap['594098']; getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponse); - getVariationStub.withArgs(configObj, experiment, user).returns(fakeDecisionResponseWithArgs); + getVariationStub.withArgs('sync', configObj, experiment, user, sinon.match.any, sinon.match.any).returns(fakeDecisionResponseWithArgs); }); it('returns a decision with a variation in the experiment the feature is attached to', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; const expectedDecision = { + cmabUuid: undefined, experiment: configObj.experimentIdMap['594098'], variation: configObj.variationIdMap['594096'], decisionSource: DECISION_SOURCES.FEATURE_TEST, @@ -1268,9 +1270,12 @@ describe('lib/core/decision_service', function() { assert.deepEqual(decision, expectedDecision); sinon.assert.calledWith( getVariationStub, + 'sync', configObj, experiment, user, + sinon.match.any, + sinon.match.any ); }); }); @@ -1316,10 +1321,10 @@ describe('lib/core/decision_service', function() { optimizely: {}, userId: 'user1', }); - fakeDecisionResponseWithArgs = { - result: 'var', + fakeDecisionResponseWithArgs = Value.of('sync', { + result: { variationKey: 'var' }, reasons: [], - }; + }); getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponseWithArgs); getVariationStub.withArgs(configObj, 'exp_with_group', user).returns(fakeDecisionResponseWithArgs); @@ -1328,6 +1333,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in an experiment in a group', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { + cmabUuid: undefined, experiment: configObj.experimentIdMap['595010'], variation: configObj.variationIdMap['595008'], decisionSource: DECISION_SOURCES.FEATURE_TEST, @@ -1566,10 +1572,10 @@ describe('lib/core/decision_service', function() { var feature; var getVariationStub; var bucketStub; - fakeDecisionResponse = { - result: null, + fakeDecisionResponse = Value.of('sync', { + result: {}, reasons: [], - }; + }); var fakeBucketStubDecisionResponse = { result: '599057', reasons: [], @@ -1662,6 +1668,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'group_2_exp_1'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '38901', @@ -1689,6 +1696,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'group_2_exp_2'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '38905', @@ -1716,6 +1724,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'group_2_exp_3'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '38906', @@ -1798,6 +1807,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'test_experiment3'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '222239', @@ -1826,6 +1836,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'test_experiment4'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '222240', @@ -1854,6 +1865,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'test_experiment5'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '222241', diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 386606cc9..e8f29cf84 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -48,6 +48,7 @@ import { UserProfileService, Variation, } from '../../shared_types'; + import { INVALID_USER_ID, INVALID_VARIATION_KEY, @@ -68,6 +69,9 @@ import { VARIATION_REMOVED_FOR_USER, } from 'log_message'; import { OptimizelyError } from '../../error/optimizly_error'; +import { CmabService } from './cmab/cmab_service'; +import { Maybe, OpType, OpValue } from '../../utils/type'; +import { Value } from '../../utils/promise/operation_value'; export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; export const RETURNING_STORED_VARIATION = @@ -102,17 +106,22 @@ export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; +export const CMAB_NOT_SUPPORTED_IN_SYNC = 'CMAB is not supported in sync mode.'; +export const CMAB_FETCH_FAILED = 'Failed to fetch CMAB data for experiment %s.'; +export const CMAB_FETCHED_VARIATION_INVALID = 'Fetched variation %s for cmab experiment %s is invalid.'; export interface DecisionObj { experiment: Experiment | null; variation: Variation | null; decisionSource: string; + cmabUuid?: string; } interface DecisionServiceOptions { userProfileService?: UserProfileService; logger?: LoggerFacade; UNSTABLE_conditionEvaluators: unknown; + cmabService: CmabService; } interface DeliveryRuleResponse<T, K> extends DecisionResponse<T> { @@ -124,6 +133,18 @@ interface UserProfileTracker { isProfileUpdated: boolean; } +type VarationKeyWithCmabParams = { + variationKey?: string; + cmabUuid?: string; +}; +export type DecisionReason = [string, ...any[]]; +export type VariationResult = DecisionResponse<VarationKeyWithCmabParams>; +export type DecisionResult = DecisionResponse<DecisionObj>; +type VariationIdWithCmabParams = { + variationId? : string; + cmabUuid?: string; +}; +export type DecideOptionsMap = Partial<Record<OptimizelyDecideOption, boolean>>; /** * Optimizely's decision service that determines which variation of an experiment the user will be allocated to. * @@ -144,12 +165,18 @@ export class DecisionService { private audienceEvaluator: AudienceEvaluator; private forcedVariationMap: { [key: string]: { [id: string]: string } }; private userProfileService?: UserProfileService; + private cmabService: CmabService; constructor(options: DecisionServiceOptions) { this.logger = options.logger; this.audienceEvaluator = createAudienceEvaluator(options.UNSTABLE_conditionEvaluators, this.logger); this.forcedVariationMap = {}; this.userProfileService = options.userProfileService; + this.cmabService = options.cmabService; + } + + private isCmab(experiment: Experiment): boolean { + return !!experiment.cmab; } /** @@ -161,54 +188,51 @@ export class DecisionService { * @returns {DecisionResponse<string|null>} - A DecisionResponse containing the variation the user is bucketed into, * along with the decision reasons. */ - private resolveVariation( + private resolveVariation<OP extends OpType>( + op: OP, configObj: ProjectConfig, experiment: Experiment, user: OptimizelyUserContext, - shouldIgnoreUPS: boolean, - userProfileTracker: UserProfileTracker - ): DecisionResponse<string | null> { + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, VariationResult> { const userId = user.getUserId(); - const attributes = user.getAttributes(); - // by default, the bucketing ID should be the user ID - const bucketingId = this.getBucketingId(userId, attributes); const experimentKey = experiment.key; - const decideReasons: (string | number)[][] = []; - if (!isActive(configObj, experimentKey)) { this.logger?.info(EXPERIMENT_NOT_RUNNING, experimentKey); - decideReasons.push([EXPERIMENT_NOT_RUNNING, experimentKey]); - return { - result: null, - reasons: decideReasons, - }; + return Value.of(op, { + result: {}, + reasons: [[EXPERIMENT_NOT_RUNNING, experimentKey]], + }); } + const decideReasons: DecisionReason[] = []; + const decisionForcedVariation = this.getForcedVariation(configObj, experimentKey, userId); decideReasons.push(...decisionForcedVariation.reasons); const forcedVariationKey = decisionForcedVariation.result; if (forcedVariationKey) { - return { - result: forcedVariationKey, + return Value.of(op, { + result: { variationKey: forcedVariationKey }, reasons: decideReasons, - }; + }); } const decisionWhitelistedVariation = this.getWhitelistedVariation(experiment, userId); decideReasons.push(...decisionWhitelistedVariation.reasons); let variation = decisionWhitelistedVariation.result; if (variation) { - return { - result: variation.key, + return Value.of(op, { + result: { variationKey: variation.key }, reasons: decideReasons, - }; + }); } - // check for sticky bucketing if decide options do not include shouldIgnoreUPS - if (!shouldIgnoreUPS) { + // check for sticky bucketing + if (userProfileTracker) { variation = this.getStoredVariation(configObj, experiment, userId, userProfileTracker.userProfile); if (variation) { this.logger?.info( @@ -223,14 +247,13 @@ export class DecisionService { experimentKey, userId, ]); - return { - result: variation.key, + return Value.of(op, { + result: { variationKey: variation.key }, reasons: decideReasons, - }; + }); } } - // Perform regular targeting and bucketing const decisionifUserIsInAudience = this.checkIfUserIsInAudience( configObj, experiment, @@ -250,57 +273,124 @@ export class DecisionService { userId, experimentKey, ]); - return { - result: null, + return Value.of(op, { + result: {}, reasons: decideReasons, - }; + }); } - const bucketerParams = this.buildBucketerParams(configObj, experiment, bucketingId, userId); - const decisionVariation = bucket(bucketerParams); - decideReasons.push(...decisionVariation.reasons); - const variationId = decisionVariation.result; - if (variationId) { - variation = configObj.variationIdMap[variationId]; - } - if (!variation) { - this.logger?.debug( - USER_HAS_NO_VARIATION, + const decisionVariationValue = this.isCmab(experiment) ? + this.getDecisionForCmabExperiment(op, configObj, experiment, user, decideOptions) : + this.getDecisionFromBucketer(op, configObj, experiment, user); + + return decisionVariationValue.then((variationResult): Value<OP, VariationResult> => { + decideReasons.push(...variationResult.reasons); + if (variationResult.error) { + return Value.of(op, { + error: true, + result: {}, + reasons: decideReasons, + }); + } + + const variationId = variationResult.result.variationId; + variation = variationId ? configObj.variationIdMap[variationId] : null; + if (!variation) { + this.logger?.debug( + USER_HAS_NO_VARIATION, + userId, + experimentKey, + ); + decideReasons.push([ + USER_HAS_NO_VARIATION, + userId, + experimentKey, + ]); + return Value.of(op, { + result: {}, + reasons: decideReasons, + }); + } + + this.logger?.info( + USER_HAS_VARIATION, userId, + variation.key, experimentKey, ); decideReasons.push([ - USER_HAS_NO_VARIATION, + USER_HAS_VARIATION, userId, + variation.key, experimentKey, ]); - return { - result: null, + // update experiment bucket map if decide options do not include shouldIgnoreUPS + if (userProfileTracker) { + this.updateUserProfile(experiment, variation, userProfileTracker); + } + + return Value.of(op, { + result: { variationKey: variation.key, cmabUuid: variationResult.result.cmabUuid }, reasons: decideReasons, - }; - } + }); + }); + } - this.logger?.info( - USER_HAS_VARIATION, - userId, - variation.key, - experimentKey, - ); - decideReasons.push([ - USER_HAS_VARIATION, - userId, - variation.key, - experimentKey, - ]); - // persist bucketing if decide options do not include shouldIgnoreUPS - if (!shouldIgnoreUPS) { - this.updateUserProfile(experiment, variation, userProfileTracker); + private getDecisionForCmabExperiment<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext, + decideOptions: DecideOptionsMap, + ): Value<OP, DecisionResponse<VariationIdWithCmabParams>> { + if (op === 'sync') { + return Value.of(op, { + error: false, // this is not considered an error, the evaluation should continue to next rule + result: {}, + reasons: [[CMAB_NOT_SUPPORTED_IN_SYNC]], + }); } - return { - result: variation.key, - reasons: decideReasons, - }; + const cmabPromise = this.cmabService.getDecision(configObj, user, experiment.id, decideOptions).then( + (cmabDecision) => { + return { + error: false, + result: cmabDecision, + reasons: [] as DecisionReason[], + }; + } + ).catch((ex: any) => { + this.logger?.error(CMAB_FETCH_FAILED, experiment.key); + return { + error: true, + result: {}, + reasons: [[CMAB_FETCH_FAILED, experiment.key]] as DecisionReason[], + }; + }); + + return Value.of(op, cmabPromise); + } + + private getDecisionFromBucketer<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext + ): Value<OP, DecisionResponse<VariationIdWithCmabParams>> { + const userId = user.getUserId(); + const attributes = user.getAttributes(); + + // by default, the bucketing ID should be the user ID + const bucketingId = this.getBucketingId(userId, attributes); + const bucketerParams = this.buildBucketerParams(configObj, experiment, bucketingId, userId); + + const decisionVariation = bucket(bucketerParams); + return Value.of(op, { + result: { + variationId: decisionVariation.result || undefined, + }, + reasons: decisionVariation.reasons, + }); } /** @@ -316,24 +406,25 @@ export class DecisionService { configObj: ProjectConfig, experiment: Experiment, user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} + options: DecideOptionsMap = {} ): DecisionResponse<string | null> { const shouldIgnoreUPS = options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; - const userProfileTracker: UserProfileTracker = { - isProfileUpdated: false, - userProfile: null, - } - if(!shouldIgnoreUPS) { - userProfileTracker.userProfile = this.resolveExperimentBucketMap(user.getUserId(), user.getAttributes()); - } + const userProfileTracker: Maybe<UserProfileTracker> = shouldIgnoreUPS ? undefined + : { + isProfileUpdated: false, + userProfile: this.resolveExperimentBucketMap('sync', user.getUserId(), user.getAttributes()).get(), + }; - const result = this.resolveVariation(configObj, experiment, user, shouldIgnoreUPS, userProfileTracker); + const result = this.resolveVariation('sync', configObj, experiment, user, options, userProfileTracker).get(); - if(!shouldIgnoreUPS) { - this.saveUserProfile(user.getUserId(), userProfileTracker) + if(userProfileTracker) { + this.saveUserProfile('sync', user.getUserId(), userProfileTracker) } - return result + return { + result: result.result.variationKey || null, + reasons: result.reasons, + } } /** @@ -342,15 +433,19 @@ export class DecisionService { * @param {UserAttributes} attributes * @return {ExperimentBucketMap} finalized copy of experiment_bucket_map */ - private resolveExperimentBucketMap( + private resolveExperimentBucketMap<OP extends OpType>( + op: OP, userId: string, - attributes?: UserAttributes - ): ExperimentBucketMap { - attributes = attributes || {}; - - const userProfile = this.getUserProfile(userId) || {} as UserProfile; - const attributeExperimentBucketMap = attributes[CONTROL_ATTRIBUTES.STICKY_BUCKETING_KEY]; - return { ...userProfile.experiment_bucket_map, ...attributeExperimentBucketMap as any }; + attributes: UserAttributes = {}, + ): Value<OP, ExperimentBucketMap> { + const fromAttributes = (attributes[CONTROL_ATTRIBUTES.STICKY_BUCKETING_KEY] || {}) as any as ExperimentBucketMap; + return this.getUserProfile(op, userId).then((userProfile) => { + const fromUserProfileService = userProfile?.experiment_bucket_map || {}; + return Value.of(op, { + ...fromUserProfileService, + ...fromAttributes, + }); + }); } /** @@ -364,7 +459,7 @@ export class DecisionService { experiment: Experiment, userId: string ): DecisionResponse<Variation | null> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; if (experiment.forcedVariations && experiment.forcedVariations.hasOwnProperty(userId)) { const forcedVariationKey = experiment.forcedVariations[userId]; if (experiment.variationKeyMap.hasOwnProperty(forcedVariationKey)) { @@ -424,7 +519,7 @@ export class DecisionService { user: OptimizelyUserContext, loggingKey?: string | number, ): DecisionResponse<boolean> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; const experimentAudienceConditions = getExperimentAudienceConditions(configObj, experiment.id); const audiencesById = getAudiencesById(configObj); this.logger?.debug( @@ -522,29 +617,28 @@ export class DecisionService { /** * Get the user profile with the given user ID * @param {string} userId - * @return {UserProfile|null} the stored user profile or null if one isn't found + * @return {UserProfile} the stored user profile or an empty profile if one isn't found or error */ - private getUserProfile(userId: string): UserProfile | null { - const userProfile = { + private getUserProfile<OP extends OpType>(op: OP, userId: string): Value<OP, UserProfile> { + const emptyProfile = { user_id: userId, experiment_bucket_map: {}, }; - if (!this.userProfileService) { - return userProfile; - } - - try { - return this.userProfileService.lookup(userId); - } catch (ex: any) { - this.logger?.error( - USER_PROFILE_LOOKUP_ERROR, - userId, - ex.message, - ); + if (this.userProfileService) { + try { + return Value.of(op, this.userProfileService.lookup(userId)); + } catch (ex: any) { + this.logger?.error( + USER_PROFILE_LOOKUP_ERROR, + userId, + ex.message, + ); + } + return Value.of(op, emptyProfile); } - return null; + return Value.of(op, emptyProfile); } private updateUserProfile( @@ -569,31 +663,42 @@ export class DecisionService { * @param {string} userId * @param {ExperimentBucketMap} experimentBucketMap */ - private saveUserProfile( + private saveUserProfile<OP extends OpType>( + op: OP, userId: string, userProfileTracker: UserProfileTracker - ): void { + ): Value<OP, unknown> { const { userProfile, isProfileUpdated } = userProfileTracker; - if (!this.userProfileService || !userProfile || !isProfileUpdated) { - return; + if (!userProfile || !isProfileUpdated) { + return Value.of(op, undefined); } - try { - this.userProfileService.save({ - user_id: userId, - experiment_bucket_map: userProfile, - }); + if (op === 'sync' && !this.userProfileService) { + return Value.of(op, undefined); + } - this.logger?.info( - SAVED_USER_VARIATION, - userId, - ); - } catch (ex: any) { - this.logger?.error(USER_PROFILE_SAVE_ERROR, userId, ex.message); + if (this.userProfileService) { + try { + this.userProfileService.save({ + user_id: userId, + experiment_bucket_map: userProfile, + }); + + this.logger?.info( + SAVED_USER_VARIATION, + userId, + ); + } catch (ex: any) { + this.logger?.error(USER_PROFILE_SAVE_ERROR, userId, ex.message); + } + return Value.of(op, undefined); } + + return Value.of(op, undefined); } + /** * Determines variations for the specified feature flags. * @@ -604,62 +709,99 @@ export class DecisionService { * @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with * experiment, variation, decisionSource properties, and decision reasons. */ - getVariationsForFeatureList(configObj: ProjectConfig, + getVariationsForFeatureList( + configObj: ProjectConfig, + featureFlags: FeatureFlag[], + user: OptimizelyUserContext, + options: DecideOptionsMap = {}): DecisionResult[] { + return this.resolveVariationsForFeatureList('sync', configObj, featureFlags, user, options).get(); + } + + resolveVariationsForFeatureList<OP extends OpType>( + op: OP, + configObj: ProjectConfig, featureFlags: FeatureFlag[], user: OptimizelyUserContext, - options: { [key: string]: boolean } = {}): DecisionResponse<DecisionObj>[] { + options: DecideOptionsMap): Value<OP, DecisionResult[]> { const userId = user.getUserId(); const attributes = user.getAttributes(); const decisions: DecisionResponse<DecisionObj>[] = []; - const userProfileTracker : UserProfileTracker = { - isProfileUpdated: false, - userProfile: null, - } + // const userProfileTracker : UserProfileTracker = { + // isProfileUpdated: false, + // userProfile: null, + // } const shouldIgnoreUPS = !!options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; - if(!shouldIgnoreUPS) { - userProfileTracker.userProfile = this.resolveExperimentBucketMap(userId, attributes); - } + const userProfileTrackerValue: Value<OP, Maybe<UserProfileTracker>> = shouldIgnoreUPS ? Value.of(op, undefined) + : this.resolveExperimentBucketMap(op, userId, attributes).then((userProfile) => { + return Value.of(op, { + isProfileUpdated: false, + userProfile: userProfile, + }); + }); - for(const feature of featureFlags) { - const decideReasons: (string | number)[][] = []; - const decisionVariation = this.getVariationForFeatureExperiment(configObj, feature, user, shouldIgnoreUPS, userProfileTracker); - decideReasons.push(...decisionVariation.reasons); - const experimentDecision = decisionVariation.result; + return userProfileTrackerValue.then((userProfileTracker) => { + const flagResults = featureFlags.map((feature) => this.resolveVariationForFlag(op, configObj, feature, user, options, userProfileTracker)); + const opFlagResults = Value.all(op, flagResults); - if (experimentDecision.variation !== null) { - decisions.push({ - result: experimentDecision, - reasons: decideReasons, - }); - continue; + return opFlagResults.then(() => { + if(userProfileTracker) { + this.saveUserProfile(op, userId, userProfileTracker); + } + return opFlagResults; + }); + }); + } + + private resolveVariationForFlag<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + feature: FeatureFlag, + user: OptimizelyUserContext, + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker + ): Value<OP, DecisionResult> { + const decideReasons: DecisionReason[] = []; + + const forcedDecisionResponse = this.findValidatedForcedDecision(configObj, user, feature.key); + decideReasons.push(...forcedDecisionResponse.reasons); + + if (forcedDecisionResponse.result) { + return Value.of(op, { + result: { + variation: forcedDecisionResponse.result, + experiment: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + } + + return this.getVariationForFeatureExperiment(op, configObj, feature, user, decideOptions, userProfileTracker).then((experimentDecision) => { + if (experimentDecision.error || experimentDecision.result.variation !== null) { + return Value.of(op, experimentDecision); } - const decisionRolloutVariation = this.getVariationForRollout(configObj, feature, user); - decideReasons.push(...decisionRolloutVariation.reasons); - const rolloutDecision = decisionRolloutVariation.result; + decideReasons.push(...experimentDecision.reasons); + + const rolloutDecision = this.getVariationForRollout(configObj, feature, user); + decideReasons.push(...rolloutDecision.reasons); + const rolloutDecisionResult = rolloutDecision.result; const userId = user.getUserId(); - - if (rolloutDecision.variation) { + + if (rolloutDecisionResult.variation) { this.logger?.debug(USER_IN_ROLLOUT, userId, feature.key); decideReasons.push([USER_IN_ROLLOUT, userId, feature.key]); } else { this.logger?.debug(USER_NOT_IN_ROLLOUT, userId, feature.key); decideReasons.push([USER_NOT_IN_ROLLOUT, userId, feature.key]); } - - decisions.push({ - result: rolloutDecision, + + return Value.of(op, { + result: rolloutDecisionResult, reasons: decideReasons, }); - } - - if(!shouldIgnoreUPS) { - this.saveUserProfile(userId, userProfileTracker); - } - - return decisions; - + }); } /** @@ -681,68 +823,111 @@ export class DecisionService { configObj: ProjectConfig, feature: FeatureFlag, user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} + options: DecideOptionsMap = {} ): DecisionResponse<DecisionObj> { - return this.getVariationsForFeatureList(configObj, [feature], user, options)[0] + return this.resolveVariationsForFeatureList('sync', configObj, [feature], user, options).get()[0] } - private getVariationForFeatureExperiment( + private getVariationForFeatureExperiment<OP extends OpType>( + op: OP, configObj: ProjectConfig, feature: FeatureFlag, user: OptimizelyUserContext, - shouldIgnoreUPS: boolean, - userProfileTracker: UserProfileTracker - ): DecisionResponse<DecisionObj> { + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, DecisionResult> { - const decideReasons: (string | number)[][] = []; - let variationKey = null; - let decisionVariation; - let index; - let variationForFeatureExperiment; - - // Check if the feature flag is under an experiment and the the user is bucketed into one of these experiments - if (feature.experimentIds.length > 0) { - // Evaluate each experiment ID and return the first bucketed experiment variation - for (index = 0; index < feature.experimentIds.length; index++) { - const experiment = getExperimentFromId(configObj, feature.experimentIds[index], this.logger); - if (experiment) { - decisionVariation = this.getVariationFromExperimentRule(configObj, feature.key, experiment, user, shouldIgnoreUPS, userProfileTracker); - decideReasons.push(...decisionVariation.reasons); - variationKey = decisionVariation.result; - if (variationKey) { - let variation = null; - variation = experiment.variationKeyMap[variationKey]; - if (!variation) { - variation = getFlagVariationByKey(configObj, feature.key, variationKey); - } - variationForFeatureExperiment = { - experiment: experiment, - variation: variation, - decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; - - return { - result: variationForFeatureExperiment, - reasons: decideReasons, - } - } - } - } - } else { + // const decideReasons: DecisionReason[] = []; + // let variationKey = null; + // let decisionVariation; + // let index; + // let variationForFeatureExperiment; + + if (feature.experimentIds.length === 0) { this.logger?.debug(FEATURE_HAS_NO_EXPERIMENTS, feature.key); - decideReasons.push([FEATURE_HAS_NO_EXPERIMENTS, feature.key]); + return Value.of(op, { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: [ + [FEATURE_HAS_NO_EXPERIMENTS, feature.key], + ], + }); } + + return this.traverseFeatureExperimentList(op, configObj, feature, 0, user, [], decideOptions, userProfileTracker); + } - variationForFeatureExperiment = { - experiment: null, - variation: null, - decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; + private traverseFeatureExperimentList<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + feature: FeatureFlag, + fromIndex: number, + user: OptimizelyUserContext, + decideReasons: DecisionReason[], + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, DecisionResult> { + const experimentIds = feature.experimentIds; + if (fromIndex >= experimentIds.length) { + return Value.of(op, { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + } - return { - result: variationForFeatureExperiment, - reasons: decideReasons, - }; + const experiment = getExperimentFromId(configObj, experimentIds[fromIndex], this.logger); + if (!experiment) { + return this.traverseFeatureExperimentList( + op, configObj, feature, fromIndex + 1, user, decideReasons, decideOptions, userProfileTracker); + } + + const decisionVariationValue = this.getVariationFromExperimentRule( + op, configObj, feature.key, experiment, user, decideOptions, userProfileTracker, + ); + + return decisionVariationValue.then((decisionVariation) => { + decideReasons.push(...decisionVariation.reasons); + + if (decisionVariation.error) { + return Value.of(op, { + error: true, + result: { + experiment, + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + } + + if(!decisionVariation.result.variationKey) { + return this.traverseFeatureExperimentList( + op, configObj, feature, fromIndex + 1, user, decideReasons, decideOptions, userProfileTracker); + } + + const variationKey = decisionVariation.result.variationKey; + let variation: Variation | null = experiment.variationKeyMap[variationKey]; + if (!variation) { + variation = getFlagVariationByKey(configObj, feature.key, variationKey); + } + + return Value.of(op, { + result: { + cmabUuid: decisionVariation.result.cmabUuid, + experiment, + variation, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + }); } private getVariationForRollout( @@ -750,7 +935,7 @@ export class DecisionService { feature: FeatureFlag, user: OptimizelyUserContext, ): DecisionResponse<DecisionObj> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; let decisionObj: DecisionObj; if (!feature.rolloutId) { this.logger?.debug(NO_ROLLOUT_EXISTS, feature.key); @@ -882,7 +1067,7 @@ export class DecisionService { ruleKey?: string ): DecisionResponse<Variation | null> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; const forcedDecision = user.getForcedDecision({ flagKey, ruleKey }); let variation = null; let variationKey; @@ -1015,7 +1200,7 @@ export class DecisionService { experimentKey: string, userId: string ): DecisionResponse<string | null> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; const experimentToVariationMap = this.forcedVariationMap[userId]; if (!experimentToVariationMap) { this.logger?.debug( @@ -1170,15 +1355,16 @@ export class DecisionService { } } - private getVariationFromExperimentRule( + private getVariationFromExperimentRule<OP extends OpType>( + op: OP, configObj: ProjectConfig, flagKey: string, rule: Experiment, user: OptimizelyUserContext, - shouldIgnoreUPS: boolean, - userProfileTracker: UserProfileTracker - ): DecisionResponse<string | null> { - const decideReasons: (string | number)[][] = []; + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, VariationResult> { + const decideReasons: DecisionReason[] = []; // check forced decision first const forcedDecisionResponse = this.findValidatedForcedDecision(configObj, user, flagKey, rule.key); @@ -1186,19 +1372,31 @@ export class DecisionService { const forcedVariation = forcedDecisionResponse.result; if (forcedVariation) { - return { - result: forcedVariation.key, + return Value.of(op, { + result: { variationKey: forcedVariation.key }, reasons: decideReasons, - }; + }); } - const decisionVariation = this.resolveVariation(configObj, rule, user, shouldIgnoreUPS, userProfileTracker); - decideReasons.push(...decisionVariation.reasons); - const variationKey = decisionVariation.result; + const decisionVariationValue = this.resolveVariation(op, configObj, rule, user, decideOptions, userProfileTracker); - return { - result: variationKey, - reasons: decideReasons, - }; + return decisionVariationValue.then((variationResult) => { + decideReasons.push(...variationResult.reasons); + return Value.of(op, { + error: variationResult.error, + result: variationResult.result, + reasons: decideReasons, + }); + }); + + // return response; + + // decideReasons.push(...decisionVariation.reasons); + // const variationKey = decisionVariation.result; + + // return { + // result: variationKey, + // reasons: decideReasons, + // }; } private getVariationFromDeliveryRule( @@ -1208,7 +1406,7 @@ export class DecisionService { ruleIndex: number, user: OptimizelyUserContext ): DeliveryRuleResponse<Variation | null, boolean> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; let skipToEveryoneElse = false; // check forced decision first diff --git a/lib/entrypoint.universal.test-d.ts b/lib/entrypoint.universal.test-d.ts index 1b5afb060..fb91017b6 100644 --- a/lib/entrypoint.universal.test-d.ts +++ b/lib/entrypoint.universal.test-d.ts @@ -48,10 +48,11 @@ import { import { LogLevel } from './logging/logger'; import { OptimizelyDecideOption } from './shared_types'; +import { UniversalConfig } from './index.universal'; export type UniversalEntrypoint = { // client factory - createInstance: (config: Config) => Client | null; + createInstance: (config: UniversalConfig) => Client | null; // config manager related exports createStaticProjectConfigManager: (config: StaticConfigManagerConfig) => OpaqueConfigManager; diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index 520ab4d0b..6266d8a5a 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -72,6 +72,7 @@ type Metadata = { rule_type: string; variation_key: string; enabled: boolean; + cmab_uuid?: string; } export type SnapshotEvent = { @@ -156,7 +157,7 @@ function makeConversionSnapshot(conversion: ConversionEvent): Snapshot { } function makeDecisionSnapshot(event: ImpressionEvent): Snapshot { - const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event + const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled, cmabUuid } = event const layerId = layer ? layer.id : null const experimentId = experiment?.id ?? '' const variationId = variation?.id ?? '' @@ -174,6 +175,7 @@ function makeDecisionSnapshot(event: ImpressionEvent): Snapshot { rule_type: ruleType, variation_key: variationKey, enabled: enabled, + cmab_uuid: cmabUuid, }, }, ], diff --git a/lib/event_processor/event_builder/user_event.tests.js b/lib/event_processor/event_builder/user_event.tests.js index 085435f09..19964e931 100644 --- a/lib/event_processor/event_builder/user_event.tests.js +++ b/lib/event_processor/event_builder/user_event.tests.js @@ -142,6 +142,7 @@ describe('user_event', function() { flagKey: 'flagkey1', ruleType: 'experiment', enabled: true, + cmabUuid: undefined, }); }); }); @@ -235,6 +236,7 @@ describe('user_event', function() { flagKey: 'flagkey1', ruleType: 'experiment', enabled: false, + cmabUuid: undefined, }); }); }); diff --git a/lib/event_processor/event_builder/user_event.ts b/lib/event_processor/event_builder/user_event.ts index 970d12937..e2e52bedc 100644 --- a/lib/event_processor/event_builder/user_event.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -76,6 +76,7 @@ export type ImpressionEvent = BaseUserEvent & { flagKey: string; ruleType: string; enabled: boolean; + cmabUuid?: string; }; export type EventTags = { @@ -144,6 +145,7 @@ export const buildImpressionEvent = function({ const experimentId = decision.getExperimentId(decisionObj); const variationKey = decision.getVariationKey(decisionObj); const variationId = decision.getVariationId(decisionObj); + const cmabUuid = decisionObj.cmabUuid; const layerId = experimentId !== null ? getLayerId(configObj, experimentId) : null; @@ -185,6 +187,7 @@ export const buildImpressionEvent = function({ flagKey: flagKey, ruleType: ruleType, enabled: enabled, + cmabUuid, }; }; diff --git a/lib/index.browser.ts b/lib/index.browser.ts index b8c31659d..98c7a11d2 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -18,6 +18,7 @@ import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_b import { getOptimizelyInstance } from './client_factory'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums'; +import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser'; /** * Creates an instance of the Optimizely class @@ -26,7 +27,10 @@ import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums'; * null on error */ export const createInstance = function(config: Config): Client | null { - const client = getOptimizelyInstance(config); + const client = getOptimizelyInstance({ + ...config, + requestHandler: new BrowserRequestHandler(), + }); if (client) { const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; diff --git a/lib/index.node.ts b/lib/index.node.ts index cb1802af8..348c8c3d9 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -17,6 +17,7 @@ import { NODE_CLIENT_ENGINE } from './utils/enums'; import { Client, Config } from './shared_types'; import { getOptimizelyInstance } from './client_factory'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +import { NodeRequestHandler } from './utils/http_request_handler/request_handler.node'; /** * Creates an instance of the Optimizely class @@ -28,6 +29,7 @@ export const createInstance = function(config: Config): Client | null { const nodeConfig = { ...config, clientEnging: config.clientEngine || NODE_CLIENT_ENGINE, + requestHandler: new NodeRequestHandler(), } return getOptimizelyInstance(nodeConfig); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 48a8ee35c..fbdf9c8a0 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -20,6 +20,7 @@ import { Client, Config } from './shared_types'; import { getOptimizelyInstance } from './client_factory'; import { REACT_NATIVE_JS_CLIENT_ENGINE } from './utils/enums'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser'; /** * Creates an instance of the Optimizely class @@ -31,6 +32,7 @@ export const createInstance = function(config: Config): Client | null { const rnConfig = { ...config, clientEngine: config.clientEngine || REACT_NATIVE_JS_CLIENT_ENGINE, + requestHandler: new BrowserRequestHandler(), } return getOptimizelyInstance(rnConfig); diff --git a/lib/index.universal.ts b/lib/index.universal.ts index 5df959975..6bd233a32 100644 --- a/lib/index.universal.ts +++ b/lib/index.universal.ts @@ -17,13 +17,19 @@ import { Client, Config } from './shared_types'; import { getOptimizelyInstance } from './client_factory'; import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums'; +import { RequestHandler } from './utils/http_request_handler/http'; + +export type UniversalConfig = Config & { + requestHandler: RequestHandler; +} + /** * Creates an instance of the Optimizely class * @param {Config} config * @return {Client|null} the Optimizely client object * null on error */ -export const createInstance = function(config: Config): Client | null { +export const createInstance = function(config: UniversalConfig): Client | null { return getOptimizelyInstance(config); }; diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index e6a2260a3..d820f59ee 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -108,5 +108,6 @@ export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to pag export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; export const CMAB_FETCH_FAILED = 'CMAB decision fetch failed with status: %s'; export const INVALID_CMAB_FETCH_RESPONSE = 'Invalid CMAB fetch response'; +export const PROMISE_NOT_ALLOWED = "Promise value is not allowed in sync operation"; export const messages: string[] = []; diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 593cb84ba..1d41c7982 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -25,6 +25,13 @@ import { createProjectConfig } from '../project_config/project_config'; import { getMockLogger } from '../tests/mock/mock_logger'; import { createOdpManager } from '../odp/odp_manager_factory.node'; import { extractOdpManager } from '../odp/odp_manager_factory'; +import { Value } from '../utils/promise/operation_value'; +import { getDecisionTestDatafile } from '../tests/decision_test_datafile'; +import { DECISION_SOURCES } from '../utils/enums'; +import OptimizelyUserContext from '../optimizely_user_context'; +import { newErrorDecision } from '../optimizely_decision'; +import { EventDispatcher } from '../shared_types'; +import { ImpressionEvent } from '../event_processor/event_builder/user_event'; describe('Optimizely', () => { const eventDispatcher = { @@ -52,10 +59,121 @@ describe('Optimizely', () => { eventProcessor, odpManager, disposable: true, + cmabService: {} as any }); expect(projectConfigManager.makeDisposable).toHaveBeenCalled(); expect(eventProcessor.makeDisposable).toHaveBeenCalled(); expect(odpManager.makeDisposable).toHaveBeenCalled(); }); + + describe('decideAsync', () => { + it('should return an error decision with correct reasons if decisionService returns error', async () => { + const projectConfig = createProjectConfig(getDecisionTestDatafile()); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionService = optimizely.decisionService; + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: true, + result: { + variation: null, + experiment: projectConfig.experimentKeyMap['exp_3'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons:[ + ['test reason %s', '1'], + ['test reason %s', '2'], + ] + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision).toEqual(newErrorDecision('flag_1', user, ['test reason 1', 'test reason 2'])); + }); + + it('should include cmab uuid in dispatched event if decisionService returns a cmab uuid', async () => { + const projectConfig = createProjectConfig(getDecisionTestDatafile()); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionService = optimizely.decisionService; + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + cmabUuid: 'uuid-cmab', + variation: projectConfig.variationIdMap['5003'], + experiment: projectConfig.experimentKeyMap['exp_3'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.ruleKey).toBe('exp_3'); + expect(decision.flagKey).toBe('flag_1'); + expect(decision.variationKey).toBe('variation_3'); + expect(decision.enabled).toBe(true); + + expect(eventProcessor.process).toHaveBeenCalledOnce(); + const event = processSpy.mock.calls[0][0] as ImpressionEvent; + expect(event.cmabUuid).toBe('uuid-cmab'); + }); + }); }); diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index cd74a2d00..ac1a0fd96 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -224,6 +224,8 @@ describe('lib/optimizely', function() { save: function() {}, }; + const cmabService = {}; + new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, @@ -231,12 +233,14 @@ describe('lib/optimizely', function() { jsonSchemaValidator: jsonSchemaValidator, userProfileService: userProfileServiceInstance, notificationCenter, + cmabService, eventProcessor, }); sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: userProfileServiceInstance, logger: createdLogger, + cmabService, UNSTABLE_conditionEvaluators: undefined, }); @@ -251,6 +255,8 @@ describe('lib/optimizely', function() { save: function() {}, }; + const cmabService = {}; + new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, @@ -259,12 +265,14 @@ describe('lib/optimizely', function() { userProfileService: invalidUserProfile, notificationCenter, eventProcessor, + cmabService, }); sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: undefined, logger: createdLogger, UNSTABLE_conditionEvaluators: undefined, + cmabService, }); // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); @@ -360,6 +368,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -424,6 +433,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variationWithAudience', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -493,6 +503,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variationWithAudience', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -567,6 +578,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variationWithAudience', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -674,6 +686,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'var2exp2', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -736,6 +749,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'var2exp1', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -2237,6 +2251,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -2299,6 +2314,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -4534,6 +4550,7 @@ describe('lib/optimizely', function() { rule_type: 'feature-test', variation_key: 'variation_with_traffic', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -5261,6 +5278,7 @@ describe('lib/optimizely', function() { userId, }); var decision = optlyInstance.decide(user, flagKey); + expect(decision.reasons).to.include( sprintf(USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, userId, experimentKey, groupId) ); @@ -6222,6 +6240,7 @@ describe('lib/optimizely', function() { rule_type: 'feature-test', variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -6449,6 +6468,7 @@ describe('lib/optimizely', function() { rule_type: 'feature-test', variation_key: 'control', enabled: false, + cmab_uuid: undefined, }, }, ], @@ -6652,6 +6672,7 @@ describe('lib/optimizely', function() { rule_type: 'rollout', variation_key: '', enabled: false, + cmab_uuid: undefined, }, }, ], diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index bf8e6c717..4b4e749a3 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -60,7 +60,7 @@ import { NODE_CLIENT_ENGINE, CLIENT_VERSION, } from '../utils/enums'; -import { Fn, Maybe } from '../utils/type'; +import { Fn, Maybe, OpType, OpValue } from '../utils/type'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; @@ -102,6 +102,8 @@ import { import { ErrorNotifier } from '../error/error_notifier'; import { ErrorReporter } from '../error/error_reporter'; import { OptimizelyError } from '../error/optimizly_error'; +import { Value } from '../utils/promise/operation_value'; +import { CmabService } from '../core/decision_service/cmab/cmab_service'; const DEFAULT_ONREADY_TIMEOUT = 30000; @@ -118,6 +120,7 @@ type DecisionReasons = (string | number)[]; export type OptimizelyOptions = { projectConfigManager: ProjectConfigManager; UNSTABLE_conditionEvaluators?: unknown; + cmabService: CmabService; clientEngine: string; clientVersion?: string; errorNotifier?: ErrorNotifier; @@ -225,6 +228,7 @@ export default class Optimizely extends BaseService implements Client { this.decisionService = createDecisionService({ userProfileService: userProfileService, + cmabService: config.cmabService, logger: this.logger, UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, }); @@ -1396,6 +1400,18 @@ export default class Optimizely extends BaseService implements Client { return this.decideForKeys(user, [key], options, true)[key]; } + async decideAsync(user: OptimizelyUserContext, key: string, options: OptimizelyDecideOption[] = []): Promise<OptimizelyDecision> { + const configObj = this.getProjectConfig(); + + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decide'); + return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); + } + + const result = await this.decideForKeysAsync(user, [key], options, true); + return result[key]; + } + /** * Get all decide options. * @param {OptimizelyDecideOption[]} options decide options @@ -1525,20 +1541,38 @@ export default class Optimizely extends BaseService implements Client { options: OptimizelyDecideOption[] = [], ignoreEnabledFlagOption?:boolean ): Record<string, OptimizelyDecision> { + return this.getDecisionForKeys('sync', user, keys, options, ignoreEnabledFlagOption).get(); + } + + decideForKeysAsync( + user: OptimizelyUserContext, + keys: string[], + options: OptimizelyDecideOption[] = [], + ignoreEnabledFlagOption?:boolean + ): Promise<Record<string, OptimizelyDecision>> { + return this.getDecisionForKeys('async', user, keys, options, ignoreEnabledFlagOption).get(); + } + + private getDecisionForKeys<OP extends OpType>( + op: OP, + user: OptimizelyUserContext, + keys: string[], + options: OptimizelyDecideOption[] = [], + ignoreEnabledFlagOption?:boolean + ): Value<OP, Record<string, OptimizelyDecision>> { const decisionMap: Record<string, OptimizelyDecision> = {}; const flagDecisions: Record<string, DecisionObj> = {}; const decisionReasonsMap: Record<string, DecisionReasons[]> = {}; - const flagsWithoutForcedDecision = []; - const validKeys = []; const configObj = this.getProjectConfig() if (!configObj) { this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideForKeys'); - return decisionMap; + return Value.of(op, decisionMap); } + if (keys.length === 0) { - return decisionMap; + return Value.of(op, decisionMap); } const allDecideOptions = this.getAllDecideOptions(options); @@ -1547,6 +1581,8 @@ export default class Optimizely extends BaseService implements Client { delete allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY]; } + const validFlags: FeatureFlag[] = []; + for(const key of keys) { const feature = configObj.featureKeyMap[key]; if (!feature) { @@ -1555,40 +1591,42 @@ export default class Optimizely extends BaseService implements Client { continue; } - validKeys.push(key); - const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key); - decisionReasonsMap[key] = forcedDecisionResponse.reasons - const variation = forcedDecisionResponse.result; - - if (variation) { - flagDecisions[key] = { - experiment: null, - variation: variation, - decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; - } else { - flagsWithoutForcedDecision.push(feature) - } + validFlags.push(feature); } - const decisionList = this.decisionService.getVariationsForFeatureList(configObj, flagsWithoutForcedDecision, user, allDecideOptions); + return this.decisionService.resolveVariationsForFeatureList(op, configObj, validFlags, user, allDecideOptions) + .then((decisionList) => { + for(let i = 0; i < validFlags.length; i++) { + const key = validFlags[i].key; + const decision = decisionList[i]; + + if(decision.error) { + decisionMap[key] = newErrorDecision(key, user, decision.reasons.map(r => sprintf(r[0], ...r.slice(1)))); + } else { + flagDecisions[key] = decision.result; + decisionReasonsMap[key] = decision.reasons; + } + } - for(let i = 0; i < flagsWithoutForcedDecision.length; i++) { - const key = flagsWithoutForcedDecision[i].key; - const decision = decisionList[i]; - flagDecisions[key] = decision.result; - decisionReasonsMap[key] = [...decisionReasonsMap[key], ...decision.reasons]; - } + for(const validFlag of validFlags) { + const validKey = validFlag.key; - for(const validKey of validKeys) { - const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj); + // if there is already a value for this flag, that must have come from + // the newErrorDecision above, so we skip it + if (decisionMap[validKey]) { + continue; + } - if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) { - decisionMap[validKey] = decision; - } - } + const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj); + + if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) { + decisionMap[validKey] = decision; + } + } - return decisionMap; + return Value.of(op, decisionMap); + }, + ); } /** @@ -1613,6 +1651,22 @@ export default class Optimizely extends BaseService implements Client { return this.decideForKeys(user, allFlagKeys, options); } + async decideAllAsync( + user: OptimizelyUserContext, + options: OptimizelyDecideOption[] = [] + ): Promise<Record<string, OptimizelyDecision>> { + const decisionMap: { [key: string]: OptimizelyDecision } = {}; + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideAll'); + return decisionMap; + } + + const allFlagKeys = Object.keys(configObj.featureKeyMap); + + return this.decideForKeysAsync(user, allFlagKeys, options); + } + /** * Updates ODP Config with most recent ODP key, host, pixelUrl, and segments from the project config */ diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 4db7c0da1..6f7bfc5e8 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -67,8 +67,9 @@ export interface BucketerParams { } export interface DecisionResponse<T> { + readonly error?: boolean; readonly result: T; - readonly reasons: (string | number)[][]; + readonly reasons: [string, ...any[]][]; } export type UserAttributeValue = string | number | boolean | null; diff --git a/lib/tests/decision_test_datafile.ts b/lib/tests/decision_test_datafile.ts index 84c72de90..5048d2549 100644 --- a/lib/tests/decision_test_datafile.ts +++ b/lib/tests/decision_test_datafile.ts @@ -48,6 +48,26 @@ const testDatafile = { conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", id: "4003" }, + { + name: "age_94", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4004" + }, + { + name: "age_95", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4005" + }, + { + name: "age_96", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4006" + }, + { + name: "age_97", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4007" + }, { id: "$opt_dummy_audience", name: "Optimizely-Generated Audience for Backwards Compatibility", @@ -117,6 +137,63 @@ const testDatafile = { ], id: "4003" }, + { + name: "age_94", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 94 + } + ] + ] + ], + id: "4004" + }, + { + name: "age_95", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 95 + } + ] + ] + ], + id: "4005" + }, + { + name: "age_96", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 96 + } + ] + ] + ], + id: "4006" + }, ], variables: [], environmentKey: "production", @@ -393,8 +470,10 @@ const testDatafile = { forcedVariations: { }, - audienceIds: [], - audienceConditions: [] + audienceConditions: [ + "or", + "4002" + ] }, { id: "2003", @@ -428,7 +507,13 @@ const testDatafile = { }, audienceIds: [], - audienceConditions: [] + audienceConditions: [ + "or", + "4003" + ], + cmab: { + attributes: ["7001"], + } }, { id: "2004", diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 573857d00..bb5ca5e73 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -111,5 +111,5 @@ export { NOTIFICATION_TYPES } from '../../notification_center/type'; */ export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute - - +export const DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 * 1000; // 30 minutes +export const DEFAULT_CMAB_CACHE_SIZE = 1000; diff --git a/lib/utils/promise/operation_value.ts b/lib/utils/promise/operation_value.ts new file mode 100644 index 000000000..7f7aa3779 --- /dev/null +++ b/lib/utils/promise/operation_value.ts @@ -0,0 +1,50 @@ +import { PROMISE_NOT_ALLOWED } from '../../message/error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { OpType, OpValue } from '../type'; + + +const isPromise = (val: any): boolean => { + return val && typeof val.then === 'function'; +} + +/** + * A class that wraps a value that can be either a synchronous value or a promise and provides + * a promise like interface. This class is used to handle both synchronous and asynchronous values + * in a uniform way. + */ +export class Value<OP extends OpType, V> { + constructor(public op: OP, public val: OpValue<OP, V>) {} + + get(): OpValue<OP, V> { + return this.val; + } + + then<NV>(fn: (v: V) => Value<OP, NV>): Value<OP, NV> { + if (this.op === 'sync') { + const newVal = fn(this.val as V); + return Value.of(this.op, newVal.get() as NV); + } + return Value.of(this.op, (this.val as Promise<V>).then(fn) as Promise<NV>); + } + + static all = <OP extends OpType, V>(op: OP, vals: Value<OP, V>[]): Value<OP, V[]> => { + if (op === 'sync') { + const values = vals.map(v => v.get() as V); + return Value.of(op, values); + } + + const promises = vals.map(v => v.get() as Promise<V>); + return Value.of(op, Promise.all(promises)); + } + + static of<OP extends OpType, V>(op: OP, val: V | Promise<V>): Value<OP, V> { + if (op === 'sync') { + if (isPromise(val)) { + throw new OptimizelyError(PROMISE_NOT_ALLOWED); + } + return new Value(op, val as OpValue<OP, V>); + } + + return new Value(op, Promise.resolve(val) as OpValue<OP, V>); + } +} diff --git a/lib/utils/type.ts b/lib/utils/type.ts index 0ddc6fc3c..a6c31d769 100644 --- a/lib/utils/type.ts +++ b/lib/utils/type.ts @@ -28,3 +28,6 @@ export type AsyncProducer<T> = () => Promise<T>; export type Maybe<T> = T | undefined; export type Either<A, B> = { type: 'left', value: A } | { type: 'right', value: B }; + +export type OpType = 'sync' | 'async'; +export type OpValue<O extends OpType, V> = O extends 'sync' ? V : Promise<V>; From 8947949e080a3ad8b94a003871e6932fc2884bb3 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 4 Apr 2025 00:16:43 +0600 Subject: [PATCH 139/200] [FSSDK-11392] add support for async user profile service (#1022) --- lib/client_factory.ts | 2 + lib/core/decision_service/index.spec.ts | 421 ++++++++++++++++++++++++ lib/core/decision_service/index.ts | 24 ++ lib/optimizely/index.tests.js | 2 + lib/optimizely/index.ts | 3 + lib/shared_types.ts | 6 + 6 files changed, 458 insertions(+) diff --git a/lib/client_factory.ts b/lib/client_factory.ts index 187334cc4..42c650fd6 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -48,6 +48,7 @@ export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client | clientVersion, jsonSchemaValidator, userProfileService, + userProfileServiceAsync, defaultDecideOptions, disposable, requestHandler, @@ -75,6 +76,7 @@ export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client | clientVersion: clientVersion || CLIENT_VERSION, jsonSchemaValidator, userProfileService, + userProfileServiceAsync, defaultDecideOptions, disposable, logger, diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts index f3459ef0e..e2a186eca 100644 --- a/lib/core/decision_service/index.spec.ts +++ b/lib/core/decision_service/index.spec.ts @@ -67,11 +67,13 @@ type MockCmabService = { type DecisionServiceInstanceOpt = { logger?: boolean; userProfileService?: boolean; + userProfileServiceAsync?: boolean; } type DecisionServiceInstance = { logger?: MockLogger; userProfileService?: MockUserProfileService; + userProfileServiceAsync?: MockUserProfileService; cmabService: MockCmabService; decisionService: DecisionService; } @@ -83,6 +85,11 @@ const getDecisionService = (opt: DecisionServiceInstanceOpt = {}): DecisionServi save: vi.fn(), } : undefined; + const userProfileServiceAsync = opt.userProfileServiceAsync ? { + lookup: vi.fn(), + save: vi.fn(), + } : undefined; + const cmabService = { getDecision: vi.fn(), }; @@ -90,6 +97,7 @@ const getDecisionService = (opt: DecisionServiceInstanceOpt = {}): DecisionServi const decisionService = new DecisionService({ logger, userProfileService, + userProfileServiceAsync, UNSTABLE_conditionEvaluators: {}, cmabService, }); @@ -97,6 +105,7 @@ const getDecisionService = (opt: DecisionServiceInstanceOpt = {}): DecisionServi return { logger, userProfileService, + userProfileServiceAsync, decisionService, cmabService, }; @@ -1442,6 +1451,270 @@ describe('DecisionService', () => { {}, ); }); + + it('should use userProfileServiceAsync if available and sync user profile service is unavialable', async () => { + const { decisionService, cmabService, userProfileServiceAsync } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + }); + + userProfileServiceAsync?.lookup.mockImplementation((userId: string) => { + if (userId === 'tester-1') { + return Promise.resolve({ + user_id: 'tester-1', + experiment_bucket_map: { + '2003': { + variation_id: '5001', + }, + }, + }); + } + return Promise.resolve(null); + }); + + userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve()); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user1 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-1', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const user2 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-2', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user1, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(cmabService.getDecision).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledWith('tester-1'); + + const value2 = decisionService.resolveVariationsForFeatureList('async', config, [feature], user2, {}).get(); + expect(value2).toBeInstanceOf(Promise); + + const variation2 = (await value2)[0]; + expect(variation2.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledTimes(2); + expect(userProfileServiceAsync?.lookup).toHaveBeenNthCalledWith(2, 'tester-2'); + expect(userProfileServiceAsync?.save).toHaveBeenCalledTimes(1); + expect(userProfileServiceAsync?.save).toHaveBeenCalledWith({ + user_id: 'tester-2', + experiment_bucket_map: { + '2003': { + variation_id: '5003', + }, + }, + }); + }); + + it('should log error and perform normal decision fetch if async userProfile lookup fails', async () => { + const { decisionService, cmabService, userProfileServiceAsync, logger } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + logger: true, + }); + + userProfileServiceAsync?.lookup.mockImplementation((userId: string) => { + return Promise.reject(new Error('I am an error')); + }); + + userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve()); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledWith('tester'); + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + + expect(logger?.error).toHaveBeenCalledTimes(1); + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_LOOKUP_ERROR, 'tester', 'I am an error'); + }); + + it('should log error async userProfile save fails', async () => { + const { decisionService, cmabService, userProfileServiceAsync, logger } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + logger: true, + }); + + userProfileServiceAsync?.lookup.mockResolvedValue(null); + + userProfileServiceAsync?.save.mockRejectedValue(new Error('I am an error')); + + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledWith('tester'); + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + + expect(userProfileServiceAsync?.save).toHaveBeenCalledTimes(1); + expect(userProfileServiceAsync?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2003': { + variation_id: '5003', + }, + }, + }); + expect(logger?.error).toHaveBeenCalledTimes(1); + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_SAVE_ERROR, 'tester', 'I am an error'); + }); + + it('should use the sync user profile service if both sync and async ups are provided', async () => { + const { decisionService, userProfileService, userProfileServiceAsync, cmabService } = getDecisionService({ + userProfileService: true, + userProfileServiceAsync: true, + }); + + userProfileService?.lookup.mockReturnValue(null); + userProfileService?.save.mockReturnValue(null); + + userProfileServiceAsync?.lookup.mockResolvedValue(null); + userProfileServiceAsync?.save.mockResolvedValue(null); + + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2003': { + variation_id: '5003', + }, + }, + }); + + expect(userProfileServiceAsync?.lookup).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.save).not.toHaveBeenCalled(); + }); }); describe('resolveVariationForFeatureList - sync', () => { @@ -1493,6 +1766,154 @@ describe('DecisionService', () => { expect(cmabService.getDecision).not.toHaveBeenCalled(); }); + + it('should ignore async user profile service', async () => { + const { decisionService, userProfileServiceAsync } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + }); + + userProfileServiceAsync?.lookup.mockResolvedValue({ + user_id: 'tester', + experiment_bucket_map: { + '2002': { + variation_id: '5001', + }, + }, + }); + userProfileServiceAsync?.save.mockResolvedValue(null); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'exp_2') { + return { + result: '5002', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // should satisfy audience condition for exp_2 and exp_3 + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user, {}).get(); + + const variation = value[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.save).not.toHaveBeenCalled(); + }); + + it('should use sync user profile service', async () => { + const { decisionService, userProfileService, userProfileServiceAsync } = getDecisionService({ + userProfileService: true, + userProfileServiceAsync: true, + }); + + userProfileService?.lookup.mockImplementation((userId: string) => { + if (userId === 'tester-1') { + return { + user_id: 'tester-1', + experiment_bucket_map: { + '2002': { + variation_id: '5001', + }, + }, + }; + } + return null; + }); + + userProfileServiceAsync?.lookup.mockResolvedValue(null); + userProfileServiceAsync?.save.mockResolvedValue(null); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'exp_2') { + return { + result: '5002', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user1 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-1', + attributes: { + age: 55, // should satisfy audience condition for exp_2 and exp_3 + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user1, {}).get(); + + const variation = value[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester-1'); + + const user2 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-2', + attributes: { + age: 55, // should satisfy audience condition for exp_2 and exp_3 + }, + }); + + const value2 = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user2, {}).get(); + const variation2 = value2[0]; + expect(variation2.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(2); + expect(userProfileService?.lookup).toHaveBeenNthCalledWith(2, 'tester-2'); + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester-2', + experiment_bucket_map: { + '2002': { + variation_id: '5002', + }, + }, + }); + + expect(userProfileServiceAsync?.lookup).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.save).not.toHaveBeenCalled(); + }); }); describe('getVariationsForFeatureList', () => { diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index e8f29cf84..370ad356c 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -46,6 +46,7 @@ import { UserAttributes, UserProfile, UserProfileService, + UserProfileServiceAsync, Variation, } from '../../shared_types'; @@ -119,6 +120,7 @@ export interface DecisionObj { interface DecisionServiceOptions { userProfileService?: UserProfileService; + userProfileServiceAsync?: UserProfileServiceAsync; logger?: LoggerFacade; UNSTABLE_conditionEvaluators: unknown; cmabService: CmabService; @@ -165,6 +167,7 @@ export class DecisionService { private audienceEvaluator: AudienceEvaluator; private forcedVariationMap: { [key: string]: { [id: string]: string } }; private userProfileService?: UserProfileService; + private userProfileServiceAsync?: UserProfileServiceAsync; private cmabService: CmabService; constructor(options: DecisionServiceOptions) { @@ -172,6 +175,7 @@ export class DecisionService { this.audienceEvaluator = createAudienceEvaluator(options.UNSTABLE_conditionEvaluators, this.logger); this.forcedVariationMap = {}; this.userProfileService = options.userProfileService; + this.userProfileServiceAsync = options.userProfileServiceAsync; this.cmabService = options.cmabService; } @@ -638,6 +642,17 @@ export class DecisionService { return Value.of(op, emptyProfile); } + if (this.userProfileServiceAsync && op === 'async') { + return Value.of(op, this.userProfileServiceAsync.lookup(userId).catch((ex: any) => { + this.logger?.error( + USER_PROFILE_LOOKUP_ERROR, + userId, + ex.message, + ); + return emptyProfile; + })); + } + return Value.of(op, emptyProfile); } @@ -695,6 +710,15 @@ export class DecisionService { return Value.of(op, undefined); } + if (this.userProfileServiceAsync) { + return Value.of(op, this.userProfileServiceAsync.save({ + user_id: userId, + experiment_bucket_map: userProfile, + }).catch((ex: any) => { + this.logger?.error(USER_PROFILE_SAVE_ERROR, userId, ex.message); + })); + } + return Value.of(op, undefined); } diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index ac1a0fd96..21209e67d 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -239,6 +239,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: userProfileServiceInstance, + userProfileServiceAsync: undefined, logger: createdLogger, cmabService, UNSTABLE_conditionEvaluators: undefined, @@ -270,6 +271,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: undefined, + userProfileServiceAsync: undefined, logger: createdLogger, UNSTABLE_conditionEvaluators: undefined, cmabService, diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 4b4e749a3..42e70ff48 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -36,6 +36,7 @@ import { FeatureVariableValue, OptimizelyDecision, Client, + UserProfileServiceAsync, } from '../shared_types'; import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; @@ -130,6 +131,7 @@ export type OptimizelyOptions = { }; logger?: LoggerFacade; userProfileService?: UserProfileService | null; + userProfileServiceAsync?: UserProfileServiceAsync | null; defaultDecideOptions?: OptimizelyDecideOption[]; odpManager?: OdpManager; vuidManager?: VuidManager @@ -228,6 +230,7 @@ export default class Optimizely extends BaseService implements Client { this.decisionService = createDecisionService({ userProfileService: userProfileService, + userProfileServiceAsync: config.userProfileServiceAsync || undefined, cmabService: config.cmabService, logger: this.logger, UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 6f7bfc5e8..2ca797f27 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -97,6 +97,11 @@ export interface UserProfileService { save(profile: UserProfile): void; } +export interface UserProfileServiceAsync { + lookup(userId: string): Promise<UserProfile>; + save(profile: UserProfile): Promise<void>; +} + export interface DatafileManagerConfig { sdkKey: string; datafile?: string; @@ -361,6 +366,7 @@ export interface Config { errorNotifier?: OpaqueErrorNotifier; // user profile that contains user information userProfileService?: UserProfileService; + userProfileServiceAsync?: UserProfileServiceAsync; // dafault options for decide API defaultDecideOptions?: OptimizelyDecideOption[]; clientEngine?: string; From ad6c58248f233c3c62b71411bc7a114e5a157e5b Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 4 Apr 2025 22:38:53 +0600 Subject: [PATCH 140/200] [FSSDK-11393] fix default event flush interval (#1023) --- .../event_processor_factory.browser.ts | 5 ++ .../event_processor_factory.node.ts | 5 ++ .../event_processor_factory.react_native.ts | 5 ++ .../event_processor_factory.spec.ts | 58 +++++++++++++++---- .../event_processor_factory.ts | 19 +++--- .../event_processor_factory.universal.ts | 5 ++ 6 files changed, 79 insertions(+), 18 deletions(-) diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index 7270d9b86..39d8e169d 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -30,6 +30,9 @@ import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { SyncPrefixCache } from '../utils/cache/cache'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 1_000; + export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, ): OpaqueEventProcessor => { @@ -54,6 +57,8 @@ export const createBatchEventProcessor = ( (options.eventDispatcher ? undefined : sendBeaconEventDispatcher), flushInterval: options.flushInterval, batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, retryOptions: { maxRetries: 5, }, diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index 29ccebade..6ef10be9f 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -25,6 +25,9 @@ import { wrapEventProcessor, } from './event_processor_factory'; +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 30_000; + export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, ): OpaqueEventProcessor => { @@ -41,6 +44,8 @@ export const createBatchEventProcessor = ( closingEventDispatcher: options.closingEventDispatcher, flushInterval: options.flushInterval, batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, retryOptions: { maxRetries: 10, }, diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index 0fc5ed8ed..66e4a302b 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -31,6 +31,9 @@ import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_nati import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; import { isAvailable as isNetInfoAvailable } from '../utils/import.react_native/@react-native-community/netinfo'; +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 1_000; + export const createForwardingEventProcessor = ( eventDispatcher: EventDispatcher = defaultEventDispatcher, ): OpaqueEventProcessor => { @@ -62,6 +65,8 @@ export const createBatchEventProcessor = ( closingEventDispatcher: options.closingEventDispatcher, flushInterval: options.flushInterval, batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, retryOptions: { maxRetries: 5, }, diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts index 49f96beed..c0ea8cb5a 100644 --- a/lib/event_processor/event_processor_factory.spec.ts +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -15,7 +15,7 @@ */ import { describe, it, expect, beforeEach, vi, MockInstance } from 'vitest'; -import { DEFAULT_EVENT_BATCH_SIZE, DEFAULT_EVENT_FLUSH_INTERVAL, getBatchEventProcessor } from './event_processor_factory'; +import { getBatchEventProcessor } from './event_processor_factory'; import { BatchEventProcessor, BatchEventProcessorConfig, EventWithId,DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF } from './batch_event_processor'; import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; import { getMockSyncCache } from '../tests/mock/mock_cache'; @@ -44,6 +44,8 @@ describe('getBatchEventProcessor', () => { it('returns an instane of BatchEventProcessor if no subclass constructor is provided', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, }; const processor = getBatchEventProcessor(options); @@ -60,6 +62,8 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, }; const processor = getBatchEventProcessor(options, CustomEventProcessor); @@ -70,6 +74,8 @@ describe('getBatchEventProcessor', () => { it('does not use retry if retryOptions is not provided', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, }; const processor = getBatchEventProcessor(options); @@ -81,6 +87,8 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), retryOptions: {}, + defaultFlushInterval: 1000, + defaultBatchSize: 10, }; const processor = getBatchEventProcessor(options); @@ -94,6 +102,8 @@ describe('getBatchEventProcessor', () => { it('uses the correct maxRetries value when retryOptions is provided', () => { const options1 = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, retryOptions: { maxRetries: 10, }, @@ -105,6 +115,8 @@ describe('getBatchEventProcessor', () => { const options2 = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, retryOptions: {}, }; @@ -117,6 +129,8 @@ describe('getBatchEventProcessor', () => { it('uses exponential backoff with default parameters when retryOptions is provided without backoff values', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, retryOptions: {}, }; @@ -133,6 +147,8 @@ describe('getBatchEventProcessor', () => { it('uses exponential backoff with provided backoff values in retryOptions', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, retryOptions: { minBackoff: 1000, maxBackoff: 2000 }, }; @@ -149,6 +165,8 @@ describe('getBatchEventProcessor', () => { it('uses a IntervalRepeater with default flush interval and adds a startup log if flushInterval is not provided', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 12345, + defaultBatchSize: 77, }; const processor = getBatchEventProcessor(options); @@ -156,13 +174,13 @@ describe('getBatchEventProcessor', () => { expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); - expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, DEFAULT_EVENT_FLUSH_INTERVAL); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, 12345); const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ level: LogLevel.Warn, message: 'Invalid flushInterval %s, defaulting to %s', - params: [undefined, DEFAULT_EVENT_FLUSH_INTERVAL], + params: [undefined, 12345], }])); }); @@ -170,6 +188,8 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), flushInterval: -1, + defaultFlushInterval: 12345, + defaultBatchSize: 77, }; const processor = getBatchEventProcessor(options); @@ -177,13 +197,13 @@ describe('getBatchEventProcessor', () => { expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); - expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, DEFAULT_EVENT_FLUSH_INTERVAL); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, 12345); const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ level: LogLevel.Warn, message: 'Invalid flushInterval %s, defaulting to %s', - params: [-1, DEFAULT_EVENT_FLUSH_INTERVAL], + params: [-1, 12345], }])); }); @@ -191,6 +211,8 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), flushInterval: 12345, + defaultFlushInterval: 1000, + defaultBatchSize: 77, }; const processor = getBatchEventProcessor(options); @@ -205,21 +227,23 @@ describe('getBatchEventProcessor', () => { }); - it('uses a IntervalRepeater with default flush interval and adds a startup log if flushInterval is not provided', () => { + it('uses default batch size and adds a startup log if batchSize is not provided', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); - expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(DEFAULT_EVENT_BATCH_SIZE); + expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(77); const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ level: LogLevel.Warn, message: 'Invalid batchSize %s, defaulting to %s', - params: [undefined, DEFAULT_EVENT_BATCH_SIZE], + params: [undefined, 77], }])); }); @@ -227,24 +251,28 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), batchSize: -1, + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); - expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(DEFAULT_EVENT_BATCH_SIZE); + expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(77); const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; expect(startupLogs).toEqual(expect.arrayContaining([{ level: LogLevel.Warn, message: 'Invalid batchSize %s, defaulting to %s', - params: [-1, DEFAULT_EVENT_BATCH_SIZE], + params: [-1, 77], }])); }); it('does not use a failedEventRepeater if failedEventRetryInterval is not provided', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); @@ -257,6 +285,8 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), failedEventRetryInterval: 12345, + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); @@ -270,6 +300,8 @@ describe('getBatchEventProcessor', () => { const eventDispatcher = getMockEventDispatcher(); const options = { eventDispatcher, + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); @@ -281,6 +313,8 @@ describe('getBatchEventProcessor', () => { it('does not use any closingEventDispatcher if not provided', () => { const options = { eventDispatcher: getMockEventDispatcher(), + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); @@ -294,6 +328,8 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), closingEventDispatcher, + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); @@ -307,6 +343,8 @@ describe('getBatchEventProcessor', () => { const options = { eventDispatcher: getMockEventDispatcher(), eventStore, + defaultBatchSize: 77, + defaultFlushInterval: 12345, }; const processor = getBatchEventProcessor(options); diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index fe7f838f7..7be0a1be4 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -22,9 +22,7 @@ import { EventProcessor } from "./event_processor"; import { BatchEventProcessor, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, EventWithId, RetryConfig } from "./batch_event_processor"; import { AsyncPrefixCache, Cache, SyncPrefixCache } from "../utils/cache/cache"; -export const DEFAULT_EVENT_BATCH_SIZE = 10; -export const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; -export const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; + export const FAILED_EVENT_RETRY_INTERVAL = 20 * 1000; export const EVENT_STORE_PREFIX = 'optly_event:'; @@ -60,9 +58,12 @@ export type BatchEventProcessorOptions = { eventStore?: Cache<string>; }; -export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, 'eventDispatcher' | 'eventStore'> & { +export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, 'eventDispatcher' | 'eventStore' > & { eventDispatcher: EventDispatcher; + closingEventDispatcher?: EventDispatcher; failedEventRetryInterval?: number; + defaultFlushInterval: number; + defaultBatchSize: number; eventStore?: Cache<EventWithId>; retryOptions?: { maxRetries?: number; @@ -88,23 +89,25 @@ export const getBatchEventProcessor = ( const startupLogs: StartupLog[] = []; - let flushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; + const { defaultFlushInterval, defaultBatchSize } = options; + + let flushInterval = defaultFlushInterval; if (options.flushInterval === undefined || options.flushInterval <= 0) { startupLogs.push({ level: LogLevel.Warn, message: 'Invalid flushInterval %s, defaulting to %s', - params: [options.flushInterval, DEFAULT_EVENT_FLUSH_INTERVAL], + params: [options.flushInterval, defaultFlushInterval], }); } else { flushInterval = options.flushInterval; } - let batchSize = DEFAULT_EVENT_BATCH_SIZE; + let batchSize = defaultBatchSize; if (options.batchSize === undefined || options.batchSize <= 0) { startupLogs.push({ level: LogLevel.Warn, message: 'Invalid batchSize %s, defaulting to %s', - params: [options.batchSize, DEFAULT_EVENT_BATCH_SIZE], + params: [options.batchSize, defaultBatchSize], }); } else { batchSize = options.batchSize; diff --git a/lib/event_processor/event_processor_factory.universal.ts b/lib/event_processor/event_processor_factory.universal.ts index 40ef4a93d..7b192f96a 100644 --- a/lib/event_processor/event_processor_factory.universal.ts +++ b/lib/event_processor/event_processor_factory.universal.ts @@ -25,6 +25,9 @@ import { getPrefixEventStore, } from './event_processor_factory'; +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 1_000; + import { FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; export const createForwardingEventProcessor = ( @@ -47,6 +50,8 @@ export const createBatchEventProcessor = ( closingEventDispatcher: options.closingEventDispatcher, flushInterval: options.flushInterval, batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, retryOptions: { maxRetries: 5, }, From 55206c6c1cf17a2da7d938029ec98d34497c6a21 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 8 Apr 2025 21:55:13 +0600 Subject: [PATCH 141/200] rename experimentsIds to experimentIds in projectConfig event type (#1024) --- lib/project_config/project_config.ts | 2 +- lib/shared_types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 92b9c1ac5..5a7674668 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -63,7 +63,7 @@ interface TryCreatingProjectConfigConfig { interface Event { key: string; id: string; - experimentsIds: string[]; + experimentIds: string[]; } interface VariableUsageMap { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 2ca797f27..ea15b21e3 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -402,7 +402,7 @@ export type OptimizelyAudience = { export type OptimizelyEvent = { id: string; key: string; - experimentsIds: string[]; + experimentIds: string[]; }; export interface OptimizelyFeature { From 326b50a9b923ec79d172a6e3240b789259aade95 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 10 Apr 2025 22:52:29 +0600 Subject: [PATCH 142/200] [FSSDK-10655] reason inclusion (#1027) --- lib/core/decision_service/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 370ad356c..dbd01061c 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -803,7 +803,10 @@ export class DecisionService { return this.getVariationForFeatureExperiment(op, configObj, feature, user, decideOptions, userProfileTracker).then((experimentDecision) => { if (experimentDecision.error || experimentDecision.result.variation !== null) { - return Value.of(op, experimentDecision); + return Value.of(op, { + ...experimentDecision, + reasons: [...decideReasons, ...experimentDecision.reasons], + }); } decideReasons.push(...experimentDecision.reasons); From eb406750cfbb0492637a596ce3e6ef6a4e9620de Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 15 Apr 2025 21:00:04 +0600 Subject: [PATCH 143/200] [FSSDK-11397] replace odp pixel api usage with event api for browser (#1026) --- lib/odp/odp_manager_factory.browser.spec.ts | 36 ++++++++++++++------- lib/odp/odp_manager_factory.browser.ts | 10 +++--- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/odp/odp_manager_factory.browser.spec.ts b/lib/odp/odp_manager_factory.browser.spec.ts index 16b4183c8..d8ecc8605 100644 --- a/lib/odp/odp_manager_factory.browser.spec.ts +++ b/lib/odp/odp_manager_factory.browser.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,9 +28,9 @@ vi.mock('./odp_manager_factory', () => { import { describe, it, expect, beforeEach, vi } from 'vitest'; import { getOpaqueOdpManager, OdpManagerOptions } from './odp_manager_factory'; -import { BROWSER_DEFAULT_API_TIMEOUT, createOdpManager } from './odp_manager_factory.browser'; +import { BROWSER_DEFAULT_API_TIMEOUT, BROWSER_DEFAULT_BATCH_SIZE, BROWSER_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.browser'; import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; -import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { eventApiRequestGenerator, pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; describe('createOdpManager', () => { const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); @@ -77,25 +77,39 @@ describe('createOdpManager', () => { expect(requestHandlerOptions?.timeout).toBe(BROWSER_DEFAULT_API_TIMEOUT); }); - it('should use batchSize 1 if batchSize is not provided', () => { - const odpManager = createOdpManager({}); + it('should use the provided eventBatchSize', () => { + const odpManager = createOdpManager({ eventBatchSize: 99 }); expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; - expect(eventBatchSize).toBe(1); + expect(eventBatchSize).toBe(99); }); - it('should use batchSize 1 event if some other batchSize value is provided', () => { - const odpManager = createOdpManager({ eventBatchSize: 99 }); + it('should use the browser default eventBatchSize if none provided', () => { + const odpManager = createOdpManager({}); expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; - expect(eventBatchSize).toBe(1); + expect(eventBatchSize).toBe(BROWSER_DEFAULT_BATCH_SIZE); + }); + + it('should use the provided eventFlushInterval', () => { + const odpManager = createOdpManager({ eventFlushInterval: 9999 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(9999); + }); + + it('should use the browser default eventFlushInterval if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(BROWSER_DEFAULT_FLUSH_INTERVAL); }); - it('uses the pixel api request generator', () => { + it('uses the event api request generator', () => { const odpManager = createOdpManager({ }); expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); const { eventRequestGenerator } = mockGetOpaqueOdpManager.mock.calls[0][0]; - expect(eventRequestGenerator).toBe(pixelApiRequestGenerator); + expect(eventRequestGenerator).toBe(eventApiRequestGenerator); }); it('uses the passed options for relevant fields', () => { diff --git a/lib/odp/odp_manager_factory.browser.ts b/lib/odp/odp_manager_factory.browser.ts index bf56d82cd..e5d97d8e1 100644 --- a/lib/odp/odp_manager_factory.browser.ts +++ b/lib/odp/odp_manager_factory.browser.ts @@ -15,11 +15,12 @@ */ import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; -import { pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; -import { OdpManager } from './odp_manager'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; export const BROWSER_DEFAULT_API_TIMEOUT = 10_000; +export const BROWSER_DEFAULT_BATCH_SIZE = 10; +export const BROWSER_DEFAULT_FLUSH_INTERVAL = 1000; export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpManager => { const segmentRequestHandler = new BrowserRequestHandler({ @@ -32,9 +33,10 @@ export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpMana return getOpaqueOdpManager({ ...options, - eventBatchSize: 1, + eventBatchSize: options.eventBatchSize || BROWSER_DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || BROWSER_DEFAULT_FLUSH_INTERVAL, segmentRequestHandler, eventRequestHandler, - eventRequestGenerator: pixelApiRequestGenerator, + eventRequestGenerator: eventApiRequestGenerator, }); }; From 1d4d2904657df5d1844cf136b4414b7b32058028 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 15 Apr 2025 21:52:24 +0600 Subject: [PATCH 144/200] [FSSDK-11395] add specific type for decision notification (#1025) --- lib/common_exports.ts | 4 +- lib/core/decision_service/index.ts | 3 +- lib/entrypoint.test-d.ts | 6 +-- lib/entrypoint.universal.test-d.ts | 6 +-- lib/notification_center/type.ts | 75 +++++++++++++++++++++++++++--- lib/tests/test_data.ts | 6 +-- lib/utils/enums/index.ts | 17 ++----- lib/utils/type.ts | 8 +++- 8 files changed, 92 insertions(+), 33 deletions(-) diff --git a/lib/common_exports.ts b/lib/common_exports.ts index 947a3bcb4..93ae7db47 100644 --- a/lib/common_exports.ts +++ b/lib/common_exports.ts @@ -30,8 +30,8 @@ export { createErrorNotifier } from './error/error_notifier_factory'; export { DECISION_SOURCES, - DECISION_NOTIFICATION_TYPES, - NOTIFICATION_TYPES, } from './utils/enums'; +export { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from './notification_center/type'; + export { OptimizelyDecideOption } from './shared_types'; diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index dbd01061c..5cb82bbe8 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -19,6 +19,7 @@ import { AUDIENCE_EVALUATION_TYPES, CONTROL_ATTRIBUTES, DECISION_SOURCES, + DecisionSource, } from '../../utils/enums'; import { getAudiencesById, @@ -114,7 +115,7 @@ export const CMAB_FETCHED_VARIATION_INVALID = 'Fetched variation %s for cmab exp export interface DecisionObj { experiment: Experiment | null; variation: Variation | null; - decisionSource: string; + decisionSource: DecisionSource; cmabUuid?: string; } diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts index ee6408344..b60537ea5 100644 --- a/lib/entrypoint.test-d.ts +++ b/lib/entrypoint.test-d.ts @@ -48,10 +48,10 @@ import { import { DECISION_SOURCES, - DECISION_NOTIFICATION_TYPES, - NOTIFICATION_TYPES, } from './utils/enums'; +import { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from './notification_center/type'; + import { LogLevel } from './logging/logger'; import { OptimizelyDecideOption } from './shared_types'; @@ -89,8 +89,8 @@ export type Entrypoint = { // enums DECISION_SOURCES: typeof DECISION_SOURCES; - DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; NOTIFICATION_TYPES: typeof NOTIFICATION_TYPES; + DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; // decide options OptimizelyDecideOption: typeof OptimizelyDecideOption; diff --git a/lib/entrypoint.universal.test-d.ts b/lib/entrypoint.universal.test-d.ts index fb91017b6..cde68ae97 100644 --- a/lib/entrypoint.universal.test-d.ts +++ b/lib/entrypoint.universal.test-d.ts @@ -41,10 +41,10 @@ import { RequestHandler } from './utils/http_request_handler/http'; import { UniversalBatchEventProcessorOptions } from './event_processor/event_processor_factory.universal'; import { DECISION_SOURCES, - DECISION_NOTIFICATION_TYPES, - NOTIFICATION_TYPES, } from './utils/enums'; +import { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from './notification_center/type'; + import { LogLevel } from './logging/logger'; import { OptimizelyDecideOption } from './shared_types'; @@ -82,8 +82,8 @@ export type UniversalEntrypoint = { // enums DECISION_SOURCES: typeof DECISION_SOURCES; - DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; NOTIFICATION_TYPES: typeof NOTIFICATION_TYPES; + DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; // decide options OptimizelyDecideOption: typeof OptimizelyDecideOption; diff --git a/lib/notification_center/type.ts b/lib/notification_center/type.ts index 7dcc132ab..75cfb082f 100644 --- a/lib/notification_center/type.ts +++ b/lib/notification_center/type.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ */ import { LogEvent } from '../event_processor/event_dispatcher/event_dispatcher'; -import { EventTags, Experiment, UserAttributes, Variation } from '../shared_types'; +import { EventTags, Experiment, FeatureVariableValue, UserAttributes, VariableType, Variation } from '../shared_types'; +import { DecisionSource } from '../utils/enums'; +import { Nullable } from '../utils/type'; export type UserEventListenerPayload = { userId: string; @@ -43,16 +45,75 @@ export const DECISION_NOTIFICATION_TYPES = { FLAG: 'flag', } as const; + export type DecisionNotificationType = typeof DECISION_NOTIFICATION_TYPES[keyof typeof DECISION_NOTIFICATION_TYPES]; -// TODO: Add more specific types for decision info -export type OptimizelyDecisionInfo = Record<string, any>; +export type ExperimentAndVariationInfo = { + experimentKey: string; + variationKey: string; +} + +export type DecisionSourceInfo = Partial<ExperimentAndVariationInfo>; -export type DecisionListenerPayload = UserEventListenerPayload & { - type: DecisionNotificationType; - decisionInfo: OptimizelyDecisionInfo; +export type AbTestDecisonInfo = Nullable<ExperimentAndVariationInfo, 'variationKey'>; + +type FeatureDecisionInfo = { + featureKey: string, + featureEnabled: boolean, + source: DecisionSource, + sourceInfo: DecisionSourceInfo, } +export type FeatureTestDecisionInfo = Nullable<ExperimentAndVariationInfo, 'variationKey'>; + +export type FeatureVariableDecisionInfo = { + featureKey: string, + featureEnabled: boolean, + source: DecisionSource, + variableKey: string, + variableValue: FeatureVariableValue, + variableType: VariableType, + sourceInfo: DecisionSourceInfo, +}; + +export type VariablesMap = { [variableKey: string]: unknown } + +export type AllFeatureVariablesDecisionInfo = { + featureKey: string, + featureEnabled: boolean, + source: DecisionSource, + variableValues: VariablesMap, + sourceInfo: DecisionSourceInfo, +}; + +export type FlagDecisionInfo = { + flagKey: string, + enabled: boolean, + variationKey: string | null, + ruleKey: string | null, + variables: VariablesMap, + reasons: string[], + decisionEventDispatched: boolean, +}; + +export type DecisionInfo = { + [DECISION_NOTIFICATION_TYPES.AB_TEST]: AbTestDecisonInfo; + [DECISION_NOTIFICATION_TYPES.FEATURE]: FeatureDecisionInfo; + [DECISION_NOTIFICATION_TYPES.FEATURE_TEST]: FeatureTestDecisionInfo; + [DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE]: FeatureVariableDecisionInfo; + [DECISION_NOTIFICATION_TYPES.ALL_FEATURE_VARIABLES]: AllFeatureVariablesDecisionInfo; + [DECISION_NOTIFICATION_TYPES.FLAG]: FlagDecisionInfo; +} + +export type DecisionListenerPayloadForType<T extends DecisionNotificationType> = UserEventListenerPayload & { + type: T; + decisionInfo: DecisionInfo[T]; +} + +export type DecisionListenerPayload = { + [T in DecisionNotificationType]: DecisionListenerPayloadForType<T>; +}[DecisionNotificationType]; + export type LogEventListenerPayload = LogEvent; export type OptimizelyConfigUpdateListenerPayload = undefined; diff --git a/lib/tests/test_data.ts b/lib/tests/test_data.ts index 990096f7b..e16081939 100644 --- a/lib/tests/test_data.ts +++ b/lib/tests/test_data.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2021, 2024 Optimizely + * Copyright 2016-2021, 2024-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -3555,7 +3555,7 @@ export var getMutexFeatureTestsConfig = function() { export var rolloutDecisionObj = { experiment: null, variation: null, - decisionSource: 'rollout', + decisionSource: 'rollout' as const, }; export var featureTestDecisionObj = { @@ -3611,7 +3611,7 @@ export var featureTestDecisionObj = { variables: [], variablesMap: {} }, - decisionSource: 'feature-test', + decisionSource: 'feature-test' as const, }; var similarRuleKeyConfig = { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index bb5ca5e73..fe4fe9fbe 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2024, Optimizely + * Copyright 2016-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,15 +44,6 @@ export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; export const CLIENT_VERSION = '5.3.4'; -export const DECISION_NOTIFICATION_TYPES = { - AB_TEST: 'ab-test', - FEATURE: 'feature', - FEATURE_TEST: 'feature-test', - FEATURE_VARIABLE: 'feature-variable', - ALL_FEATURE_VARIABLES: 'all-feature-variables', - FLAG: 'flag', -}; - /* * Represents the source of a decision for feature management. When a feature * is accessed through isFeatureEnabled or getVariableValue APIs, the decision @@ -63,7 +54,9 @@ export const DECISION_SOURCES = { FEATURE_TEST: 'feature-test', ROLLOUT: 'rollout', EXPERIMENT: 'experiment', -}; +} as const; + +export type DecisionSource = typeof DECISION_SOURCES[keyof typeof DECISION_SOURCES]; export const AUDIENCE_EVALUATION_TYPES = { RULE: 'rule', @@ -104,8 +97,6 @@ export const DECISION_MESSAGES = { VARIABLE_VALUE_INVALID: 'Variable value for key "%s" is invalid or wrong type.', }; -export { NOTIFICATION_TYPES } from '../../notification_center/type'; - /** * Default milliseconds before request timeout */ diff --git a/lib/utils/type.ts b/lib/utils/type.ts index a6c31d769..c60f85d60 100644 --- a/lib/utils/type.ts +++ b/lib/utils/type.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,3 +31,9 @@ export type Either<A, B> = { type: 'left', value: A } | { type: 'right', value: export type OpType = 'sync' | 'async'; export type OpValue<O extends OpType, V> = O extends 'sync' ? V : Promise<V>; + +export type OrNull<T> = T | null; + +export type Nullable<T, K extends keyof T> = { + [P in keyof T]: P extends K ? OrNull<T[P]> : T[P]; +} From 85c0220aa8dd103afa1b7ce9b87bca2f83123cf5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 18 Apr 2025 20:59:11 +0600 Subject: [PATCH 145/200] [FSSDK-11437] pass logger to all child components (#1028) --- lib/core/decision_service/index.ts | 2 +- .../batch_event_processor.spec.ts | 30 +++++++- lib/event_processor/batch_event_processor.ts | 11 ++- lib/event_processor/event_processor.ts | 2 + .../forwarding_event_processor.ts | 1 + lib/logging/logger.ts | 17 +++-- .../odp_event_api_manager.spec.ts | 22 +++++- .../event_manager/odp_event_api_manager.ts | 13 +++- .../event_manager/odp_event_manager.spec.ts | 41 ++++++++++- lib/odp/event_manager/odp_event_manager.ts | 10 +++ lib/odp/odp_manager.spec.ts | 73 ++++++++++++++++++- lib/odp/odp_manager.ts | 15 +++- .../odp_segment_api_manager.spec.ts | 21 +++++- .../odp_segment_api_manager.ts | 15 +++- .../odp_segment_manager.spec.ts | 46 ++++++++++++ .../segment_manager/odp_segment_manager.ts | 11 +++ lib/optimizely/index.spec.ts | 42 ++++++++++- lib/optimizely/index.tests.js | 1 + lib/optimizely/index.ts | 40 +++++----- lib/project_config/optimizely_config.spec.ts | 9 +-- .../polling_datafile_manager.spec.ts | 33 ++++++++- .../polling_datafile_manager.ts | 13 +++- lib/project_config/project_config.spec.ts | 46 +++--------- .../project_config_manager.spec.ts | 42 ++++++++++- lib/project_config/project_config_manager.ts | 14 +++- lib/tests/mock/mock_logger.ts | 13 +++- lib/tests/mock/mock_project_config_manager.ts | 2 + 27 files changed, 500 insertions(+), 85 deletions(-) diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 5cb82bbe8..82b6aa028 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -355,7 +355,7 @@ export class DecisionService { reasons: [[CMAB_NOT_SUPPORTED_IN_SYNC]], }); } - + const cmabPromise = this.cmabService.getDecision(configObj, user, experiment.id, decideOptions).then( (cmabDecision) => { return { diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index 3f8809d18..a01a60f33 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -15,7 +15,7 @@ */ import { expect, describe, it, vi, beforeEach, afterEach, MockInstance } from 'vitest'; -import { EventWithId, BatchEventProcessor } from './batch_event_processor'; +import { EventWithId, BatchEventProcessor, LOGGER_NAME } from './batch_event_processor'; import { getMockSyncCache } from '../tests/mock/mock_cache'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ProcessableEvent } from './event_processor'; @@ -40,7 +40,7 @@ const exhaustMicrotasks = async (loop = 100) => { } } -describe('QueueingEventProcessor', async () => { +describe('BatchEventProcessor', async () => { beforeEach(() => { vi.useFakeTimers(); }); @@ -49,6 +49,32 @@ describe('QueueingEventProcessor', async () => { vi.useRealTimers(); }); + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher: getMockDispatcher(), + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + logger, + }); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should set name on the logger set by setLogger', () => { + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher: getMockDispatcher(), + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + }); + + processor.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + describe('start', () => { it('should log startupLogs on start', () => { const startupLogs: StartupLog[] = [ diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 97b4dd8f4..40282a8f3 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -60,6 +60,8 @@ type EventBatch = { ids: string[], } +export const LOGGER_NAME = 'BatchEventProcessor'; + export class BatchEventProcessor extends BaseService implements EventProcessor { private eventDispatcher: EventDispatcher; private closingEventDispatcher?: EventDispatcher; @@ -80,7 +82,6 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { this.closingEventDispatcher = config.closingEventDispatcher; this.batchSize = config.batchSize; this.eventStore = config.eventStore; - this.logger = config.logger; this.retryConfig = config.retryConfig; this.dispatchRepeater = config.dispatchRepeater; @@ -88,6 +89,14 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { this.failedEventRepeater = config.failedEventRepeater; this.failedEventRepeater?.setTask(() => this.retryFailedEvents()); + if (config.logger) { + this.setLogger(config.logger); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); } onDispatch(handler: Consumer<LogEvent>): Fn { diff --git a/lib/event_processor/event_processor.ts b/lib/event_processor/event_processor.ts index f33c1a7a1..3589ce3a5 100644 --- a/lib/event_processor/event_processor.ts +++ b/lib/event_processor/event_processor.ts @@ -17,6 +17,7 @@ import { ConversionEvent, ImpressionEvent } from './event_builder/user_event' import { LogEvent } from './event_dispatcher/event_dispatcher' import { Service } from '../service' import { Consumer, Fn } from '../utils/type'; +import { LoggerFacade } from '../logging/logger'; export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s export const DEFAULT_BATCH_SIZE = 10 @@ -26,4 +27,5 @@ export type ProcessableEvent = ConversionEvent | ImpressionEvent export interface EventProcessor extends Service { process(event: ProcessableEvent): Promise<unknown>; onDispatch(handler: Consumer<LogEvent>): Fn; + setLogger(logger: LoggerFacade): void; } diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 67899bddb..744ac5975 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -25,6 +25,7 @@ import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; import { SERVICE_STOPPED_BEFORE_RUNNING } from 'error_message'; import { OptimizelyError } from '../error/optimizly_error'; + class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; private eventEmitter: EventEmitter<{ dispatch: LogEvent }>; diff --git a/lib/logging/logger.ts b/lib/logging/logger.ts index 568bd2cac..8414d544a 100644 --- a/lib/logging/logger.ts +++ b/lib/logging/logger.ts @@ -43,7 +43,8 @@ export interface LoggerFacade { debug(message: string | Error, ...args: any[]): void; warn(message: string | Error, ...args: any[]): void; error(message: string | Error, ...args: any[]): void; - child(name: string): LoggerFacade; + child(name?: string): LoggerFacade; + setName(name: string): void; } export interface LogHandler { @@ -84,7 +85,7 @@ type OptimizelyLoggerConfig = { export class OptimizelyLogger implements LoggerFacade { private name?: string; - private prefix: string; + private prefix = ''; private logHandler: LogHandler; private infoResolver?: MessageResolver; private errorResolver: MessageResolver; @@ -95,11 +96,12 @@ export class OptimizelyLogger implements LoggerFacade { this.infoResolver = config.infoMsgResolver; this.errorResolver = config.errorMsgResolver; this.level = config.level; - this.name = config.name; - this.prefix = this.name ? `${this.name}: ` : ''; + if (config.name) { + this.setName(config.name); + } } - child(name: string): OptimizelyLogger { + child(name?: string): OptimizelyLogger { return new OptimizelyLogger({ logHandler: this.logHandler, infoMsgResolver: this.infoResolver, @@ -109,6 +111,11 @@ export class OptimizelyLogger implements LoggerFacade { }); } + setName(name: string): void { + this.name = name; + this.prefix = `${name}: `; + } + info(message: string | Error, ...args: any[]): void { this.log(LogLevel.Info, message, args) } diff --git a/lib/odp/event_manager/odp_event_api_manager.spec.ts b/lib/odp/event_manager/odp_event_api_manager.spec.ts index 316787821..04d74ea18 100644 --- a/lib/odp/event_manager/odp_event_api_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_api_manager.spec.ts @@ -15,7 +15,7 @@ */ import { describe, it, expect, vi } from 'vitest'; -import { DefaultOdpEventApiManager, eventApiRequestGenerator, pixelApiRequestGenerator } from './odp_event_api_manager'; +import { DefaultOdpEventApiManager, eventApiRequestGenerator, LOGGER_NAME, pixelApiRequestGenerator } from './odp_event_api_manager'; import { OdpEvent } from './odp_event'; import { OdpConfig } from '../odp_config'; @@ -41,8 +41,28 @@ const PIXEL_URL = 'https://odp.pixel.com/'; const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; +import { getMockLogger } from '../../tests/mock/mock_logger'; describe('DefaultOdpEventApiManager', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const requestHandler = getMockRequestHandler(); + + const manager = new DefaultOdpEventApiManager(requestHandler, vi.fn(), logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should set name on the logger set by setLogger', () => { + const logger = getMockLogger(); + const requestHandler = getMockRequestHandler(); + + const manager = new DefaultOdpEventApiManager(requestHandler, vi.fn()); + manager.setLogger(logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + it('should generate the event request using the correct odp config and event', async () => { const mockRequestHandler = getMockRequestHandler(); mockRequestHandler.makeRequest.mockReturnValue({ diff --git a/lib/odp/event_manager/odp_event_api_manager.ts b/lib/odp/event_manager/odp_event_api_manager.ts index 23dec6274..79154b06e 100644 --- a/lib/odp/event_manager/odp_event_api_manager.ts +++ b/lib/odp/event_manager/odp_event_api_manager.ts @@ -24,6 +24,7 @@ export type EventDispatchResponse = { }; export interface OdpEventApiManager { sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<EventDispatchResponse>; + setLogger(logger: LoggerFacade): void; } export type EventRequest = { @@ -34,6 +35,9 @@ export type EventRequest = { } export type EventRequestGenerator = (odpConfig: OdpConfig, events: OdpEvent[]) => EventRequest; + +export const LOGGER_NAME = 'OdpEventApiManager'; + export class DefaultOdpEventApiManager implements OdpEventApiManager { private logger?: LoggerFacade; private requestHandler: RequestHandler; @@ -46,9 +50,16 @@ export class DefaultOdpEventApiManager implements OdpEventApiManager { ) { this.requestHandler = requestHandler; this.requestGenerator = requestDataGenerator; - this.logger = logger; + if (logger) { + this.setLogger(logger) + } } + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + } + async sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<EventDispatchResponse> { if (events.length === 0) { return {}; diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index 9d16273b9..6fb5db08a 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; -import { DefaultOdpEventManager } from './odp_event_manager'; +import { DefaultOdpEventManager, LOGGER_NAME } from './odp_event_manager'; import { getMockRepeater } from '../../tests/mock/mock_repeater'; import { getMockLogger } from '../../tests/mock/mock_logger'; import { ServiceState } from '../../service'; @@ -46,6 +46,7 @@ const makeEvent = (id: number) => { const getMockApiManager = () => { return { sendEvents: vi.fn(), + setLogger: vi.fn(), }; }; @@ -72,6 +73,44 @@ describe('DefaultOdpEventManager', () => { expect(odpEventManager.getState()).toBe(ServiceState.New); }); + it('should set name on the logger set using setLogger', () => { + const logger = getMockLogger(); + + const manager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the event api manager when a logger is set using setLogger', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + + const apiManager = getMockApiManager(); + + const manager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + manager.setLogger(logger); + expect(apiManager.setLogger).toHaveBeenCalledWith(childLogger); + }); + it('should stay in starting state if started with a odpIntegationConfig and not resolve or reject onRunning', async () => { const odpEventManager = new DefaultOdpEventManager({ repeater: getMockRepeater(), diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 75c1a632c..a076655b5 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -34,10 +34,12 @@ import { ODP_EVENT_MANAGER_STOPPED } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; +import { LoggerFacade } from '../../logging/logger'; export interface OdpEventManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; sendEvent(event: OdpEvent): void; + setLogger(logger: LoggerFacade): void; } export type RetryConfig = { @@ -53,6 +55,8 @@ export type OdpEventManagerConfig = { retryConfig: RetryConfig, }; +export const LOGGER_NAME = 'OdpEventManager'; + export class DefaultOdpEventManager extends BaseService implements OdpEventManager { private queue: OdpEvent[] = []; private repeater: Repeater; @@ -73,6 +77,12 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag this.repeater.setTask(() => this.flush()); } + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.apiManager.setLogger(logger.child()); + } + private async executeDispatch(odpConfig: OdpConfig, batch: OdpEvent[]): Promise<unknown> { const res = await this.apiManager.sendEvents(odpConfig, batch); if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index 26c6e82e0..376a663cf 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -16,7 +16,7 @@ import { describe, it, vi, expect } from 'vitest'; -import { DefaultOdpManager } from './odp_manager'; +import { DefaultOdpManager, LOGGER_NAME } from './odp_manager'; import { ServiceState } from '../service'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { OdpConfig } from './odp_config'; @@ -25,6 +25,7 @@ import { ODP_USER_KEY } from './constant'; import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; import { OdpEventManager } from './event_manager/odp_event_manager'; import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; +import { getMockLogger } from '../tests/mock/mock_logger'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -51,6 +52,7 @@ const getMockOdpEventManager = () => { updateConfig: vi.fn(), sendEvent: vi.fn(), makeDisposable: vi.fn(), + setLogger: vi.fn(), }; }; @@ -58,10 +60,79 @@ const getMockOdpSegmentManager = () => { return { fetchQualifiedSegments: vi.fn(), updateConfig: vi.fn(), + setLogger: vi.fn(), }; }; describe('DefaultOdpManager', () => { + describe('a logger is passed in the constructor', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const manager = new DefaultOdpManager({ + logger, + eventManager: getMockOdpEventManager(), + segmentManager: getMockOdpSegmentManager(), + }); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass different child loggers to the eventManager and segmentManager', () => { + const logger = getMockLogger(); + const eventChildLogger = getMockLogger(); + const segmentChildLogger = getMockLogger(); + + logger.child.mockReturnValueOnce(eventChildLogger) + .mockReturnValueOnce(segmentChildLogger); + + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + const manager = new DefaultOdpManager({ + logger, + eventManager, + segmentManager, + }); + + expect(eventManager.setLogger).toHaveBeenCalledWith(eventChildLogger); + expect(segmentManager.setLogger).toHaveBeenCalledWith(segmentChildLogger); + }); + }); + + describe('setLogger method', () => { + it('should set name on the logger', () => { + const logger = getMockLogger(); + const manager = new DefaultOdpManager({ + eventManager: getMockOdpEventManager(), + segmentManager: getMockOdpSegmentManager(), + }); + + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const eventChildLogger = getMockLogger(); + const segmentChildLogger = getMockLogger(); + + logger.child.mockReturnValueOnce(eventChildLogger) + .mockReturnValueOnce(segmentChildLogger); + + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + const manager = new DefaultOdpManager({ + eventManager, + segmentManager, + }); + manager.setLogger(logger); + + expect(eventManager.setLogger).toHaveBeenCalledWith(eventChildLogger); + expect(segmentManager.setLogger).toHaveBeenCalledWith(segmentChildLogger); + }); + }); + it('should be in new state on construction', () => { const odpManager = new DefaultOdpManager({ segmentManager: getMockOdpSegmentManager(), diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 6e7da8769..68a2b2c79 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -39,6 +39,7 @@ export interface OdpManager extends Service { sendEvent(event: OdpEvent): void; setClientInfo(clientEngine: string, clientVersion: string): void; setVuid(vuid: string): void; + setLogger(logger: LoggerFacade): void; } export type OdpManagerConfig = { @@ -48,6 +49,8 @@ export type OdpManagerConfig = { userAgentParser?: UserAgentParser; }; +export const LOGGER_NAME = 'OdpManager'; + export class DefaultOdpManager extends BaseService implements OdpManager { private configPromise: ResolvablePromise<void>; private segmentManager: OdpSegmentManager; @@ -62,7 +65,6 @@ export class DefaultOdpManager extends BaseService implements OdpManager { super(); this.segmentManager = config.segmentManager; this.eventManager = config.eventManager; - this.logger = config.logger; this.configPromise = resolvablePromise(); @@ -80,8 +82,19 @@ export class DefaultOdpManager extends BaseService implements OdpManager { Object.entries(userAgentInfo).filter(([_, value]) => value != null && value != undefined) ); } + + if (config.logger) { + this.setLogger(config.logger); + } } + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.eventManager.setLogger(logger.child()); + this.segmentManager.setLogger(logger.child()); + } + setClientInfo(clientEngine: string, clientVersion: string): void { this.clientEngine = clientEngine; this.clientVersion = clientVersion; diff --git a/lib/odp/segment_manager/odp_segment_api_manager.spec.ts b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts index ad07894bd..7906f5745 100644 --- a/lib/odp/segment_manager/odp_segment_api_manager.spec.ts +++ b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts @@ -19,7 +19,7 @@ import { describe, it, expect } from 'vitest'; import { ODP_USER_KEY } from '../constant'; import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; import { getMockLogger } from '../../tests/mock/mock_logger'; -import { DefaultOdpSegmentApiManager } from './odp_segment_api_manager'; +import { DefaultOdpSegmentApiManager, LOGGER_NAME } from './odp_segment_api_manager'; const API_KEY = 'not-real-api-key'; const GRAPHQL_ENDPOINT = 'https://some.example.com/graphql/endpoint'; @@ -28,7 +28,24 @@ const USER_VALUE = 'tester-101'; const SEGMENTS_TO_CHECK = ['has_email', 'has_email_opted_in', 'push_on_sale']; describe('DefaultOdpSegmentApiManager', () => { - it('should return empty list without calling api when segmentsToCheck is empty', async () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + + const manager = new DefaultOdpSegmentApiManager(getMockRequestHandler(), logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should set name on the logger set by setLogger', () => { + const logger = getMockLogger(); + + const manager = new DefaultOdpSegmentApiManager(getMockRequestHandler()); + manager.setLogger(logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should return empty list without calling api when segmentsToCheck is empty', async () => { const requestHandler = getMockRequestHandler(); requestHandler.makeRequest.mockReturnValue({ abort: () => {}, diff --git a/lib/odp/segment_manager/odp_segment_api_manager.ts b/lib/odp/segment_manager/odp_segment_api_manager.ts index 1c336b298..92eeaa02e 100644 --- a/lib/odp/segment_manager/odp_segment_api_manager.ts +++ b/lib/odp/segment_manager/odp_segment_api_manager.ts @@ -20,6 +20,7 @@ import { OdpResponseSchema } from './odp_response_schema'; import { ODP_USER_KEY } from '../constant'; import { RequestHandler } from '../../utils/http_request_handler/http'; import { Response as GraphQLResponse } from '../odp_types'; +import { log } from 'console'; /** * Expected value for a qualified/valid segment */ @@ -48,15 +49,25 @@ export interface OdpSegmentApiManager { userValue: string, segmentsToCheck: string[] ): Promise<string[] | null>; + setLogger(logger: LoggerFacade): void; } +export const LOGGER_NAME = 'OdpSegmentApiManager'; + export class DefaultOdpSegmentApiManager implements OdpSegmentApiManager { - private readonly logger?: LoggerFacade; - private readonly requestHandler: RequestHandler; + private logger?: LoggerFacade; + private requestHandler: RequestHandler; constructor(requestHandler: RequestHandler, logger?: LoggerFacade) { this.requestHandler = requestHandler; + if (logger) { + this.setLogger(logger); + } + } + + setLogger(logger: LoggerFacade): void { this.logger = logger; + this.logger.setName(LOGGER_NAME); } /** diff --git a/lib/odp/segment_manager/odp_segment_manager.spec.ts b/lib/odp/segment_manager/odp_segment_manager.spec.ts index 31598dd71..550e431b5 100644 --- a/lib/odp/segment_manager/odp_segment_manager.spec.ts +++ b/lib/odp/segment_manager/odp_segment_manager.spec.ts @@ -23,6 +23,7 @@ import { OdpConfig } from '../odp_config'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { getMockLogger } from '../../tests/mock/mock_logger'; import { getMockSyncCache } from '../../tests/mock/mock_cache'; +import { LOGGER_NAME } from './odp_segment_manager'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com/'; @@ -34,6 +35,7 @@ const config = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, SEGMENTS_TO_CHECK); const getMockApiManager = () => { return { fetchSegments: vi.fn(), + setLogger: vi.fn(), }; }; @@ -41,6 +43,50 @@ const userKey: ODP_USER_KEY = ODP_USER_KEY.FS_USER_ID; const userValue = 'test-user'; describe('DefaultOdpSegmentManager', () => { + describe('a logger is passed in the constructor', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager(), logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the segmentApiManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + const manager = new DefaultOdpSegmentManager(cache, apiManager, logger); + + expect(apiManager.setLogger).toHaveBeenCalledWith(childLogger); + }); + }); + + describe('setLogger method', () => { + it('should set name on the logger', () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager()); + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.setLogger(logger); + + expect(apiManager.setLogger).toHaveBeenCalledWith(childLogger); + }); + }); + it('should return null and log error if the ODP config is not available.', async () => { const logger = getMockLogger(); const cache = getMockSyncCache<string[]>(); diff --git a/lib/odp/segment_manager/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts index d243f2a14..8ba589dd4 100644 --- a/lib/odp/segment_manager/odp_segment_manager.ts +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -29,8 +29,11 @@ export interface OdpSegmentManager { options?: Array<OptimizelySegmentOption> ): Promise<string[] | null>; updateConfig(config: OdpIntegrationConfig): void; + setLogger(logger: LoggerFacade): void; } +export const LOGGER_NAME = 'OdpSegmentManager'; + export class DefaultOdpSegmentManager implements OdpSegmentManager { private odpIntegrationConfig?: OdpIntegrationConfig; private segmentsCache: Cache<string[]>; @@ -44,7 +47,15 @@ export class DefaultOdpSegmentManager implements OdpSegmentManager { ) { this.segmentsCache = segmentsCache; this.odpSegmentApiManager = odpSegmentApiManager; + if (logger) { + this.setLogger(logger); + } + } + + setLogger(logger: LoggerFacade): void { this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.odpSegmentApiManager.setLogger(logger.child()); } /** diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 1d41c7982..dfe708de4 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; import Optimizely from '.'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; @@ -42,6 +42,10 @@ describe('Optimizely', () => { const odpManager = extractOdpManager(createOdpManager({})); const logger = getMockLogger(); + beforeEach(() => { + vi.clearAllMocks(); + }); + it('should pass disposable options to the respective services', () => { const projectConfigManager = getMockProjectConfigManager({ initConfig: createProjectConfig(testData.getTestProjectConfig()), @@ -67,6 +71,42 @@ describe('Optimizely', () => { expect(odpManager.makeDisposable).toHaveBeenCalled(); }); + it('should set child logger to respective services', () => { + const projectConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const odpManager = extractOdpManager(createOdpManager({})); + + vi.spyOn(projectConfigManager, 'setLogger'); + vi.spyOn(eventProcessor, 'setLogger'); + vi.spyOn(odpManager, 'setLogger'); + + const logger = getMockLogger(); + const configChildLogger = getMockLogger(); + const eventProcessorChildLogger = getMockLogger(); + const odpManagerChildLogger = getMockLogger(); + vi.spyOn(logger, 'child').mockReturnValueOnce(configChildLogger) + .mockReturnValueOnce(eventProcessorChildLogger) + .mockReturnValueOnce(odpManagerChildLogger); + + new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + expect(projectConfigManager.setLogger).toHaveBeenCalledWith(configChildLogger); + expect(eventProcessor.setLogger).toHaveBeenCalledWith(eventProcessorChildLogger); + expect(odpManager.setLogger).toHaveBeenCalledWith(odpManagerChildLogger); + }); + describe('decideAsync', () => { it('should return an error decision with correct reasons if decisionService returns error', async () => { const projectConfig = createProjectConfig(getDecisionTestDatafile()); diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 21209e67d..5ec683a92 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -9234,6 +9234,7 @@ describe('lib/optimizely', function() { onRunning: sinon.stub(), onTerminated: sinon.stub(), onDispatch: sinon.stub(), + setLogger: sinon.stub(), }; }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 42e70ff48..6895fcea7 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -56,8 +56,6 @@ import { DECISION_SOURCES, DECISION_MESSAGES, FEATURE_VARIABLE_TYPES, - // DECISION_NOTIFICATION_TYPES, - // NOTIFICATION_TYPES, NODE_CLIENT_ENGINE, CLIENT_VERSION, } from '../utils/enums'; @@ -178,6 +176,13 @@ export default class Optimizely extends BaseService implements Client { this.odpManager?.makeDisposable(); } + // pass a child logger to sub-components + if (this.logger) { + this.projectConfigManager.setLogger(this.logger.child()); + this.eventProcessor?.setLogger(this.logger.child()); + this.odpManager?.setLogger(this.logger.child()); + } + let decideOptionsArray = config.defaultDecideOptions ?? []; if (!Array.isArray(decideOptionsArray)) { @@ -1280,27 +1285,22 @@ export default class Optimizely extends BaseService implements Client { /** * Returns a Promise that fulfills when this instance is ready to use (meaning - * it has a valid datafile), or has failed to become ready within a period of + * it has a valid datafile), or rejects when it has failed to become ready within a period of * time (configurable by the timeout property of the options argument), or when - * this instance is closed via the close method. + * this instance is closed via the close method before it became ready. * - * If a valid datafile was provided in the constructor, the returned Promise is - * immediately fulfilled. If an sdkKey was provided, a manager will be used to - * fetch a datafile, and the returned promise will fulfill if that fetch - * succeeds or fails before the timeout. The default timeout is 30 seconds, - * which will be used if no timeout is provided in the argument options object. + * If a static project config manager with a valid datafile was provided in the constructor, + * the returned Promise is immediately fulfilled. If a polling config manager was provided, + * it will be used to fetch a datafile, and the returned promise will fulfill if that fetch + * succeeds, or it will reject if the datafile fetch does not complete before the timeout. + * The default timeout is 30 seconds. * - * The returned Promise is fulfilled with a result object containing these - * properties: - * - success (boolean): True if this instance is ready to use with a valid - * datafile, or false if this instance failed to become - * ready or was closed prior to becoming ready. - * - reason (string=): If success is false, this is a string property with - * an explanatory message. Failure could be due to - * expiration of the timeout, network errors, - * unsuccessful responses, datafile parse errors, - * datafile validation errors, or the instance being - * closed + * The returned Promise is fulfilled with an unknown result which is not needed to + * be inspected to know that the instance is ready. If the promise is fulfilled, it + * is guaranteed that the instance is ready to use. If the promise is rejected, it + * means the instance is not ready to use, and the reason for the promise rejection + * will contain an error denoting the cause of failure. + * @param {Object=} options * @param {number|undefined} options.timeout * @return {Promise} diff --git a/lib/project_config/optimizely_config.spec.ts b/lib/project_config/optimizely_config.spec.ts index ab8d3ab5d..6e9e6747b 100644 --- a/lib/project_config/optimizely_config.spec.ts +++ b/lib/project_config/optimizely_config.spec.ts @@ -25,6 +25,7 @@ import { } from '../tests/test_data'; import { Experiment } from '../shared_types'; import { LoggerFacade } from '../logging/logger'; +import { getMockLogger } from '../tests/mock/mock_logger'; const datafile: ProjectConfig = getTestProjectConfigWithFeatures(); const typedAudienceDatafile = getTypedAudiencesConfig(); @@ -53,13 +54,7 @@ describe('Optimizely Config', () => { let optimizelySimilarExperimentkeyConfigObject: OptimizelyConfig; let projectSimilarExperimentKeyConfigObject: ProjectConfig; - const logger: LoggerFacade = { - info: vi.fn(), - debug: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - child: vi.fn().mockReturnValue(this), - }; + const logger = getMockLogger(); beforeEach(() => { projectConfigObject = createProjectConfig(cloneDeep(datafile as any)); diff --git a/lib/project_config/polling_datafile_manager.spec.ts b/lib/project_config/polling_datafile_manager.spec.ts index c8f68a1cc..a5654fa5d 100644 --- a/lib/project_config/polling_datafile_manager.spec.ts +++ b/lib/project_config/polling_datafile_manager.spec.ts @@ -15,7 +15,7 @@ */ import { describe, it, expect, vi } from 'vitest'; -import { PollingDatafileManager} from './polling_datafile_manager'; +import { LOGGER_NAME, PollingDatafileManager} from './polling_datafile_manager'; import { getMockRepeater } from '../tests/mock/mock_repeater'; import { getMockAbortableRequest, getMockRequestHandler } from '../tests/mock/mock_request_handler'; import { getMockLogger } from '../tests/mock/mock_logger'; @@ -25,7 +25,38 @@ import { ServiceState, StartupLog } from '../service'; import { getMockSyncCache, getMockAsyncCache } from '../tests/mock/mock_cache'; import { LogLevel } from '../logging/logger'; + describe('PollingDatafileManager', () => { + it('should set name on the logger passed into the constructor', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const logger = getMockLogger(); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + logger, + }); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + + it('should set name on the logger set by setLogger', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const logger = getMockLogger(); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + }); + + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + it('should log polling interval below MIN_UPDATE_INTERVAL', () => { const repeater = getMockRepeater(); const requestHandler = getMockRequestHandler(); diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index a8fb9128b..bac6adc1e 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -36,6 +36,9 @@ import { SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, } from 'log_message'; import { OptimizelyError } from '../error/optimizly_error'; +import { LoggerFacade } from '../logging/logger'; + +export const LOGGER_NAME = 'PollingDatafileManager'; export class PollingDatafileManager extends BaseService implements DatafileManager { private requestHandler: RequestHandler; @@ -74,12 +77,20 @@ export class PollingDatafileManager extends BaseService implements DatafileManag this.autoUpdate = autoUpdate; this.initRetryRemaining = initRetry; this.repeater = repeater; - this.logger = logger; + + if (logger) { + this.setLogger(logger); + } const urlTemplateToUse = urlTemplate || (datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE); this.datafileUrl = sprintf(urlTemplateToUse, this.sdkKey); } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + } onUpdate(listener: Consumer<string>): Fn { return this.emitter.on('update', listener); diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index 36ffbe89a..54aa75d97 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -29,20 +29,13 @@ import { FEATURE_NOT_IN_DATAFILE, UNABLE_TO_CAST_VALUE, } from 'error_message'; +import { getMockLogger } from '../tests/mock/mock_logger'; import { VariableType } from '../shared_types'; import { OptimizelyError } from '../error/optimizly_error'; -const createLogger = (...args: any) => ({ - debug: () => {}, - info: () => {}, - warn: () => {}, - error: () => {}, - child: () => createLogger(), -}); - const buildLogMessageFromArgs = (args: any[]) => sprintf(args[1], ...args.splice(2)); const cloneDeep = (obj: any) => JSON.parse(JSON.stringify(obj)); -const logger = createLogger(); +const logger = getMockLogger(); describe('createProjectConfig', () => { let configObj: ProjectConfig; @@ -283,13 +276,12 @@ describe('createProjectConfig - cmab experiments', () => { describe('getExperimentId', () => { let testData: Record<string, any>; let configObj: ProjectConfig; - let createdLogger: any; + let createdLogger: ReturnType<typeof getMockLogger>; beforeEach(function() { testData = cloneDeep(testDatafile.getTestProjectConfig()); configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); - createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - vi.spyOn(createdLogger, 'warn'); + createdLogger = getMockLogger(); }); it('should retrieve experiment ID for valid experiment key in getExperimentId', function() { @@ -334,13 +326,12 @@ describe('getLayerId', () => { describe('getAttributeId', () => { let testData: Record<string, any>; let configObj: ProjectConfig; - let createdLogger: any; + let createdLogger: ReturnType<typeof getMockLogger>; beforeEach(function() { testData = cloneDeep(testDatafile.getTestProjectConfig()); configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); - createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - vi.spyOn(createdLogger, 'warn'); + createdLogger = getMockLogger(); }); it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { @@ -538,16 +529,12 @@ describe('getSendFlagDecisionsValue', () => { }); describe('getVariableForFeature', function() { - let featureManagementLogger: ReturnType<typeof createLogger>; + let featureManagementLogger: ReturnType<typeof getMockLogger>; let configObj: ProjectConfig; beforeEach(() => { - featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + featureManagementLogger = getMockLogger(); configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); - vi.spyOn(featureManagementLogger, 'warn'); - vi.spyOn(featureManagementLogger, 'error'); - vi.spyOn(featureManagementLogger, 'info'); - vi.spyOn(featureManagementLogger, 'debug'); }); afterEach(() => { @@ -603,16 +590,12 @@ describe('getVariableForFeature', function() { }); describe('getVariableValueForVariation', () => { - let featureManagementLogger: ReturnType<typeof createLogger>; + let featureManagementLogger: ReturnType<typeof getMockLogger>; let configObj: ProjectConfig; beforeEach(() => { - featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + featureManagementLogger = getMockLogger(); configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); - vi.spyOn(featureManagementLogger, 'warn'); - vi.spyOn(featureManagementLogger, 'error'); - vi.spyOn(featureManagementLogger, 'info'); - vi.spyOn(featureManagementLogger, 'debug'); }); afterEach(() => { @@ -695,16 +678,12 @@ describe('getVariableValueForVariation', () => { }); describe('getTypeCastValue', () => { - let featureManagementLogger: ReturnType<typeof createLogger>; + let featureManagementLogger: ReturnType<typeof getMockLogger>; let configObj: ProjectConfig; beforeEach(() => { - featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + featureManagementLogger = getMockLogger(); configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); - vi.spyOn(featureManagementLogger, 'warn'); - vi.spyOn(featureManagementLogger, 'error'); - vi.spyOn(featureManagementLogger, 'info'); - vi.spyOn(featureManagementLogger, 'debug'); }); afterEach(() => { @@ -1065,7 +1044,6 @@ describe('tryCreatingProjectConfig', () => { beforeEach(() => { mockJsonSchemaValidator = vi.fn().mockReturnValue(true); vi.spyOn(configValidator, 'validateDatafile').mockReturnValue(true); - vi.spyOn(logger, 'error'); }); afterEach(() => { diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts index acd8538ee..3e3236644 100644 --- a/lib/project_config/project_config_manager.spec.ts +++ b/lib/project_config/project_config_manager.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { describe, it, expect, vi } from 'vitest'; -import { ProjectConfigManagerImpl } from './project_config_manager'; +import { LOGGER_NAME, ProjectConfigManagerImpl } from './project_config_manager'; import { getMockLogger } from '../tests/mock/mock_logger'; import { ServiceState } from '../service'; import * as testData from '../tests/test_data'; @@ -26,6 +26,46 @@ import { wait } from '../tests/testUtils'; const cloneDeep = (x: any) => JSON.parse(JSON.stringify(x)); describe('ProjectConfigManagerImpl', () => { + describe('a logger is passed in the constructor', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger }); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + const datafileManager = getMockDatafileManager({}); + const datafileManagerSetLogger = vi.spyOn(datafileManager, 'setLogger'); + + const manager = new ProjectConfigManagerImpl({ logger, datafileManager }); + expect(datafileManagerSetLogger).toHaveBeenCalledWith(childLogger); + }); + }); + + describe('setLogger method', () => { + it('should set name on the logger', () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({}); + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + const datafileManager = getMockDatafileManager({}); + const datafileManagerSetLogger = vi.spyOn(datafileManager, 'setLogger'); + + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.setLogger(logger); + expect(datafileManagerSetLogger).toHaveBeenCalledWith(childLogger); + }); + }); + it('should reject onRunning() and log error if neither datafile nor a datafileManager is passed into the constructor', async () => { const logger = getMockLogger(); const manager = new ProjectConfigManagerImpl({ logger}); diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index b9dbf279e..95e3fa029 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -46,6 +46,9 @@ export interface ProjectConfigManager extends Service { * string into project config objects. * @param {ProjectConfigManagerConfig} config */ + +export const LOGGER_NAME = 'ProjectConfigManager'; + export class ProjectConfigManagerImpl extends BaseService implements ProjectConfigManager { private datafile?: string | object; private projectConfig?: ProjectConfig; @@ -56,10 +59,19 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf constructor(config: ProjectConfigManagerConfig) { super(); - this.logger = config.logger; this.jsonSchemaValidator = config.jsonSchemaValidator; this.datafile = config.datafile; this.datafileManager = config.datafileManager; + + if (config.logger) { + this.setLogger(config.logger); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.datafileManager?.setLogger(logger.child()); } start(): void { diff --git a/lib/tests/mock/mock_logger.ts b/lib/tests/mock/mock_logger.ts index f9ee207e4..e9d9cb4cf 100644 --- a/lib/tests/mock/mock_logger.ts +++ b/lib/tests/mock/mock_logger.ts @@ -17,12 +17,23 @@ import { vi } from 'vitest'; import { LoggerFacade } from '../../logging/logger'; -export const getMockLogger = () : LoggerFacade => { +type MockFn = ReturnType<typeof vi.fn>; +type MockLogger = { + info: MockFn; + error: MockFn; + warn: MockFn; + debug: MockFn; + child: MockFn; + setName: MockFn; +}; + +export const getMockLogger = (): MockLogger => { return { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), child: vi.fn().mockImplementation(() => getMockLogger()), + setName: vi.fn(), }; }; diff --git a/lib/tests/mock/mock_project_config_manager.ts b/lib/tests/mock/mock_project_config_manager.ts index 65c6268ab..931f37da1 100644 --- a/lib/tests/mock/mock_project_config_manager.ts +++ b/lib/tests/mock/mock_project_config_manager.ts @@ -50,6 +50,8 @@ export const getMockProjectConfigManager = (opt: MockOpt = {}): ProjectConfigMan }, pushUpdate: function(config: ProjectConfig) { this.listeners.forEach((listener: any) => listener(config)); + }, + setLogger: function(logger: any) { } } as any as ProjectConfigManager; }; From 6214c11f9f00e9a3051a8d468f9ec2f60c7ac0c7 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 24 Apr 2025 16:42:15 +0600 Subject: [PATCH 146/200] [FSSDK-11399] support traffic allocation for cmab (#1029) --- lib/core/decision_service/index.spec.ts | 132 ++++++++++++++++++++- lib/core/decision_service/index.ts | 33 +++++- lib/project_config/project_config.spec.ts | 8 +- lib/project_config/project_config.tests.js | 2 +- lib/project_config/project_config.ts | 12 +- lib/shared_types.ts | 1 + 6 files changed, 170 insertions(+), 18 deletions(-) diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts index e2a186eca..8ddc736eb 100644 --- a/lib/core/decision_service/index.spec.ts +++ b/lib/core/decision_service/index.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { describe, it, expect, vi, MockInstance, beforeEach } from 'vitest'; -import { CMAB_FETCH_FAILED, DecisionService } from '.'; +import { CMAB_DUMMY_ENTITY_ID, CMAB_FETCH_FAILED, DecisionService } from '.'; import { getMockLogger } from '../../tests/mock/mock_logger'; import OptimizelyUserContext from '../../optimizely_user_context'; import { bucket } from '../bucketer'; @@ -140,10 +140,18 @@ const verifyBucketCall = ( variationIdMap, bucketingId, } = mockBucket.mock.calls[call][0]; + let expectedTrafficAllocation = experiment.trafficAllocation; + if (experiment.cmab) { + expectedTrafficAllocation = [{ + endOfRange: experiment.cmab.trafficAllocation, + entityId: CMAB_DUMMY_ENTITY_ID, + }]; + } + expect(experimentId).toBe(experiment.id); expect(experimentKey).toBe(experiment.key); expect(userId).toBe(user.getUserId()); - expect(trafficAllocationConfig).toBe(experiment.trafficAllocation); + expect(trafficAllocationConfig).toEqual(expectedTrafficAllocation); expect(experimentKeyMap).toBe(projectConfig.experimentKeyMap); expect(experimentIdMap).toBe(projectConfig.experimentIdMap); expect(groupIdMap).toBe(projectConfig.groupIdMap); @@ -1327,7 +1335,8 @@ describe('DecisionService', () => { }); }); - it('should get decision from the cmab service if the experiment is a cmab experiment', async () => { + it('should not return variation and should not call cmab service \ + for cmab experiment if user is not bucketed into it', async () => { const { decisionService, cmabService } = getDecisionService(); const config = createProjectConfig(getDecisionTestDatafile()); @@ -1340,6 +1349,57 @@ describe('DecisionService', () => { }, }); + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'default-rollout-key') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['default-rollout-key'], + variation: config.variationIdMap['5007'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + verifyBucketCall(0, config, config.experimentKeyMap['exp_3'], user); + expect(cmabService.getDecision).not.toHaveBeenCalled(); + }); + + it('should get decision from the cmab service if the experiment is a cmab experiment \ + and user is bucketed into it', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + cmabService.getDecision.mockResolvedValue({ variationId: '5003', cmabUuid: 'uuid-test', @@ -1357,6 +1417,8 @@ describe('DecisionService', () => { decisionSource: DECISION_SOURCES.FEATURE_TEST, }); + verifyBucketCall(0, config, config.experimentKeyMap['exp_3'], user); + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); expect(cmabService.getDecision).toHaveBeenCalledWith( config, @@ -1379,6 +1441,17 @@ describe('DecisionService', () => { }, }); + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + cmabService.getDecision.mockResolvedValue({ variationId: '5003', cmabUuid: 'uuid-test', @@ -1424,6 +1497,17 @@ describe('DecisionService', () => { }, }); + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + cmabService.getDecision.mockRejectedValue(new Error('I am an error')); const feature = config.featureKeyMap['flag_1']; @@ -1474,6 +1558,17 @@ describe('DecisionService', () => { userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve()); + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + cmabService.getDecision.mockResolvedValue({ variationId: '5003', cmabUuid: 'uuid-test', @@ -1552,6 +1647,17 @@ describe('DecisionService', () => { userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve()); + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + cmabService.getDecision.mockResolvedValue({ variationId: '5003', cmabUuid: 'uuid-test', @@ -1605,6 +1711,16 @@ describe('DecisionService', () => { userProfileServiceAsync?.save.mockRejectedValue(new Error('I am an error')); + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); cmabService.getDecision.mockResolvedValue({ variationId: '5003', @@ -1669,6 +1785,16 @@ describe('DecisionService', () => { userProfileServiceAsync?.lookup.mockResolvedValue(null); userProfileServiceAsync?.save.mockResolvedValue(null); + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); cmabService.getDecision.mockResolvedValue({ variationId: '5003', diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 82b6aa028..5d5e57da9 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -27,12 +27,12 @@ import { getExperimentFromId, getExperimentFromKey, getFlagVariationByKey, - getTrafficAllocation, getVariationIdFromExperimentAndVariationKey, getVariationFromId, getVariationKeyFromId, isActive, ProjectConfig, + getTrafficAllocation, } from '../../project_config/project_config'; import { AudienceEvaluator, createAudienceEvaluator } from '../audience_evaluator'; import * as stringValidator from '../../utils/string_value_validator'; @@ -44,6 +44,7 @@ import { FeatureFlag, OptimizelyDecideOption, OptimizelyUserContext, + TrafficAllocation, UserAttributes, UserProfile, UserProfileService, @@ -148,6 +149,9 @@ type VariationIdWithCmabParams = { cmabUuid?: string; }; export type DecideOptionsMap = Partial<Record<OptimizelyDecideOption, boolean>>; + +export const CMAB_DUMMY_ENTITY_ID= '$' + /** * Optimizely's decision service that determines which variation of an experiment the user will be allocated to. * @@ -355,6 +359,23 @@ export class DecisionService { reasons: [[CMAB_NOT_SUPPORTED_IN_SYNC]], }); } + + const userId = user.getUserId(); + const attributes = user.getAttributes(); + + const bucketingId = this.getBucketingId(userId, attributes); + const bucketerParams = this.buildBucketerParams(configObj, experiment, bucketingId, userId); + + const bucketerResult = bucket(bucketerParams); + + // this means the user is not in the cmab experiment + if (bucketerResult.result !== CMAB_DUMMY_ENTITY_ID) { + return Value.of(op, { + error: false, + result: {}, + reasons: bucketerResult.reasons, + }); + } const cmabPromise = this.cmabService.getDecision(configObj, user, experiment.id, decideOptions).then( (cmabDecision) => { @@ -573,6 +594,14 @@ export class DecisionService { bucketingId: string, userId: string ): BucketerParams { + let trafficAllocationConfig: TrafficAllocation[] = getTrafficAllocation(configObj, experiment.id); + if (experiment.cmab) { + trafficAllocationConfig = [{ + entityId: CMAB_DUMMY_ENTITY_ID, + endOfRange: experiment.cmab.trafficAllocation + }]; + } + return { bucketingId, experimentId: experiment.id, @@ -581,7 +610,7 @@ export class DecisionService { experimentKeyMap: configObj.experimentKeyMap, groupIdMap: configObj.groupIdMap, logger: this.logger, - trafficAllocationConfig: getTrafficAllocation(configObj, experiment.id), + trafficAllocationConfig, userId, variationIdMap: configObj.variationIdMap, } diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index 54aa75d97..5a0259ee4 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -249,17 +249,20 @@ describe('createProjectConfig - cmab experiments', () => { it('should populate cmab field correctly', function() { const datafile = testDatafile.getTestProjectConfig(); datafile.experiments[0].cmab = { - attributes: ['808797688', '808797689'], + attributeIds: ['808797688', '808797689'], + trafficAllocation: 3141, }; datafile.experiments[2].cmab = { - attributes: ['808797689'], + attributeIds: ['808797689'], + trafficAllocation: 1414, }; const configObj = projectConfig.createProjectConfig(datafile); const experiment0 = configObj.experiments[0]; expect(experiment0.cmab).toEqual({ + trafficAllocation: 3141, attributeIds: ['808797688', '808797689'], }); @@ -268,6 +271,7 @@ describe('createProjectConfig - cmab experiments', () => { const experiment2 = configObj.experiments[2]; expect(experiment2.cmab).toEqual({ + trafficAllocation: 1414, attributeIds: ['808797689'], }); }); diff --git a/lib/project_config/project_config.tests.js b/lib/project_config/project_config.tests.js index 6e93327cc..d69afda46 100644 --- a/lib/project_config/project_config.tests.js +++ b/lib/project_config/project_config.tests.js @@ -416,7 +416,7 @@ describe('lib/core/project_config', function() { assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); assert.deepEqual(ex.params, ['invalidExperimentId']); }); - + describe('#getVariationIdFromExperimentAndVariationKey', function() { it('should return the variation id for the given experiment key and variation key', function() { assert.strictEqual( diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 5a7674668..e91c4743a 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -157,15 +157,6 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.__datafileStr = datafileStr === null ? JSON.stringify(datafileObj) : datafileStr; - /** rename cmab.attributes field from the datafile to cmab.attributeIds for each experiment */ - projectConfig.experiments.forEach(experiment => { - if (experiment.cmab) { - const attributes = (experiment.cmab as any).attributes; - delete (experiment.cmab as any).attributes; - experiment.cmab.attributeIds = attributes; - } - }); - /* * Conditions of audiences in projectConfig.typedAudiences are not * expected to be string-encoded as they are here in projectConfig.audiences. @@ -568,6 +559,7 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper throw new OptimizelyError(EXPERIMENT_KEY_NOT_IN_DATAFILE, experimentKey); }; + /** * Given an experiment id, returns the traffic allocation within that experiment * @param {ProjectConfig} projectConfig Object representing project configuration @@ -890,7 +882,6 @@ export default { getVariationKeyFromId, getVariationIdFromExperimentAndVariationKey, getExperimentFromKey, - getTrafficAllocation, getExperimentFromId, getFlagVariationByKey, getFeatureFromKey, @@ -904,4 +895,5 @@ export default { isFeatureExperiment, toDatafile, tryCreatingProjectConfig, + getTrafficAllocation, }; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index ea15b21e3..c203613a3 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -159,6 +159,7 @@ export interface Experiment { forcedVariations?: { [key: string]: string }; isRollout?: boolean; cmab?: { + trafficAllocation: number; attributeIds: string[]; }; } From 557a2e29504b5194e403d7e53d4456b28d64afcb Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 24 Apr 2025 21:49:39 +0600 Subject: [PATCH 147/200] [FSSDK-11470] Bump vitest and @vitest/coverage-istanbul (#1033) * Bump vitest and @vitest/coverage-istanbul Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) and [@vitest/coverage-istanbul](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-istanbul). These dependencies needed to be updated together. Updates `vitest` from 2.0.5 to 2.1.9 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.9/packages/vitest) Updates `@vitest/coverage-istanbul` from 2.0.5 to 2.1.9 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.9/packages/coverage-istanbul) --- updated-dependencies: - dependency-name: vitest dependency-version: 2.1.9 dependency-type: direct:development - dependency-name: "@vitest/coverage-istanbul" dependency-version: 2.1.9 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> * workflow --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/javascript.yml | 2 +- package-lock.json | 1320 +++++++++++------------------- 2 files changed, 474 insertions(+), 848 deletions(-) diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 137b1dbe2..6f7143b62 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ['16', '18', '20', '22'] + node: ['18', '20', '22'] steps: - uses: actions/checkout@v3 - name: Set up Node ${{ matrix.node }} diff --git a/package-lock.json b/package-lock.json index 4cfbad348..8a1c4aef6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -469,19 +469,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -610,12 +612,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -2482,14 +2485,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2545,6 +2548,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -2561,6 +2565,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2577,6 +2582,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2593,6 +2599,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2609,6 +2616,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2625,6 +2633,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2641,6 +2650,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2657,6 +2667,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2673,6 +2684,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2689,6 +2701,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2705,6 +2718,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2721,6 +2735,7 @@ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2737,6 +2752,7 @@ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2753,6 +2769,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2769,6 +2786,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2785,6 +2803,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2801,6 +2820,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2817,6 +2837,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2833,6 +2854,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2849,6 +2871,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -2865,6 +2888,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2881,6 +2905,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2897,6 +2922,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3892,10 +3918,11 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -4680,208 +4707,280 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", - "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", - "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", - "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", - "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", - "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", - "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", - "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", - "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", - "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", - "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", - "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", - "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", - "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", - "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", - "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", - "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -4979,17 +5078,6 @@ "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -5420,19 +5508,20 @@ } }, "node_modules/@vitest/coverage-istanbul": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.0.5.tgz", - "integrity": "sha512-BvjWKtp7fiMAeYUD0mO5cuADzn1gmjTm54jm5qUEnh/O08riczun8rI4EtQlg3bWoRo2lT3FO8DmjPDX9ZthPw==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.1.9.tgz", + "integrity": "sha512-vdYE4FkC/y2lxcN3Dcj54Bw+ericmDwiex0B8LV5F/YNYEYP1mgVwhPnHwWGAXu38qizkjOuyczKbFTALfzFKw==", "dev": true, + "license": "MIT", "dependencies": { "@istanbuljs/schema": "^0.1.3", - "debug": "^4.3.5", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-instrument": "^6.0.3", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magicast": "^0.3.4", + "magicast": "^0.3.5", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -5440,7 +5529,7 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "2.0.5" + "vitest": "2.1.9" } }, "node_modules/@vitest/coverage-istanbul/node_modules/brace-expansion": { @@ -5453,12 +5542,13 @@ } }, "node_modules/@vitest/coverage-istanbul/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5550,6 +5640,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@vitest/coverage-istanbul/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/coverage-istanbul/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5577,14 +5674,15 @@ } }, "node_modules/@vitest/expect": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", - "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -5596,15 +5694,17 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/@vitest/expect/node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -5621,6 +5721,7 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } @@ -5630,33 +5731,88 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@vitest/expect/node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/@vitest/expect/node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.16" } }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/@vitest/pretty-format": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", - "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "dev": true, + "license": "MIT", "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -5665,12 +5821,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", - "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "2.0.5", + "@vitest/utils": "2.1.9", "pathe": "^1.1.2" }, "funding": { @@ -5678,84 +5835,64 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", - "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "magic-string": "^0.30.10", + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/@vitest/spy": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", - "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", "dev": true, + "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@vitest/utils/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/utils/node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", @@ -5915,15 +6052,6 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -5962,18 +6090,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, "node_modules/acorn-import-assertions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", @@ -6708,6 +6824,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7685,58 +7802,12 @@ "node": ">= 8" } }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", "dev": true }, - "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -7779,14 +7850,6 @@ "node": ">=0.10.0" } }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/decompress-response": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", @@ -7978,21 +8041,6 @@ "void-elements": "^2.0.0" } }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -8094,20 +8142,6 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -8166,10 +8200,11 @@ } }, "node_modules/es-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", - "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", - "dev": true + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" }, "node_modules/es6-error": { "version": "4.1.1", @@ -8198,6 +8233,7 @@ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -8257,40 +8293,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint": { "version": "8.49.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", @@ -8658,6 +8660,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", @@ -9356,20 +9368,6 @@ "hermes-estree": "0.22.0" } }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -9406,22 +9404,6 @@ "node": ">=8.0.0" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -9445,24 +9427,10 @@ "node": ">=10.17.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "funding": [ { @@ -9739,14 +9707,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -11063,70 +11023,6 @@ "signal-exit": "^3.0.2" } }, - "node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -11804,13 +11700,14 @@ } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -12927,14 +12824,6 @@ "dev": true, "peer": true }, - "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -13387,20 +13276,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13487,7 +13362,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { "version": "1.1.1", @@ -13508,10 +13384,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -13610,9 +13487,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -13628,19 +13505,20 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -13648,6 +13526,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13789,14 +13668,6 @@ "node": ">= 0.10" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -13854,14 +13725,6 @@ "node": ">=0.4.x" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -14598,20 +14461,6 @@ "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", "dev": true }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, "node_modules/scheduler": { "version": "0.24.0-canary-efb381bbf-20230505", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", @@ -14840,7 +14689,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "3.0.7", @@ -14996,10 +14846,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -15107,7 +14958,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/stackframe": { "version": "1.3.4", @@ -15149,10 +15001,11 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", - "dev": true + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" }, "node_modules/stream-combiner": { "version": "0.0.4", @@ -15324,14 +15177,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -15594,13 +15439,22 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", - "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -15610,15 +15464,17 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", - "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -15642,15 +15498,6 @@ "dev": true, "peer": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -15672,20 +15519,6 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/ts-jest": { "version": "29.1.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", @@ -15985,17 +15818,6 @@ "node": ">=4" } }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -16044,18 +15866,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16124,13 +15934,14 @@ } }, "node_modules/vite": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", - "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "version": "5.4.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", + "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.41", + "postcss": "^8.4.43", "rollup": "^4.20.0" }, "bin": { @@ -16183,15 +15994,16 @@ } }, "node_modules/vite-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", - "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.5", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", - "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -16205,12 +16017,13 @@ } }, "node_modules/vite-node/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -16221,19 +16034,28 @@ } } }, + "node_modules/vite-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/vite/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" }, "node_modules/vite/node_modules/rollup": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", - "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -16243,49 +16065,55 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.0", - "@rollup/rollup-android-arm64": "4.21.0", - "@rollup/rollup-darwin-arm64": "4.21.0", - "@rollup/rollup-darwin-x64": "4.21.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", - "@rollup/rollup-linux-arm-musleabihf": "4.21.0", - "@rollup/rollup-linux-arm64-gnu": "4.21.0", - "@rollup/rollup-linux-arm64-musl": "4.21.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", - "@rollup/rollup-linux-riscv64-gnu": "4.21.0", - "@rollup/rollup-linux-s390x-gnu": "4.21.0", - "@rollup/rollup-linux-x64-gnu": "4.21.0", - "@rollup/rollup-linux-x64-musl": "4.21.0", - "@rollup/rollup-win32-arm64-msvc": "4.21.0", - "@rollup/rollup-win32-ia32-msvc": "4.21.0", - "@rollup/rollup-win32-x64-msvc": "4.21.0", + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" } }, "node_modules/vitest": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", - "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.5", - "@vitest/pretty-format": "^2.0.5", - "@vitest/runner": "2.0.5", - "@vitest/snapshot": "2.0.5", - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", - "debug": "^4.3.5", - "execa": "^8.0.1", - "magic-string": "^0.30.10", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", - "std-env": "^3.7.0", - "tinybench": "^2.8.0", - "tinypool": "^1.0.0", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.5", + "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "bin": { @@ -16300,8 +16128,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.5", - "@vitest/ui": "2.0.5", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, @@ -16326,33 +16154,22 @@ } } }, - "node_modules/vitest/node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/vitest/node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/vitest/node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -16369,17 +16186,19 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } }, "node_modules/vitest/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -16395,177 +16214,45 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/vitest/node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/vitest/node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/vitest/node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/vitest/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/vitest/node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.16" } }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", @@ -16582,20 +16269,6 @@ "node": ">=0.10.0" } }, - "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -16709,20 +16382,6 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", @@ -16739,21 +16398,6 @@ "node": ">=12" } }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -16780,6 +16424,7 @@ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -16873,25 +16518,6 @@ } } }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", From e7e9e42da358495f9542c3db95c909af73241fc3 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 25 Apr 2025 00:36:24 +0600 Subject: [PATCH 148/200] Bump rollup from 2.2.0 to 2.79.2 (#1035) Bumps [rollup](https://github.com/rollup/rollup) from 2.2.0 to 2.79.2. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.2.0...v2.79.2) --- updated-dependencies: - dependency-name: rollup dependency-version: 2.79.2 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 26 ++++++-------------------- package.json | 2 +- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a1c4aef6..8393d04a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "nyc": "^15.0.1", "prettier": "^1.19.1", "promise-polyfill": "8.1.0", - "rollup": "2.2.0", + "rollup": "2.79.2", "rollup-plugin-terser": "^5.3.0", "rollup-plugin-typescript2": "^0.27.1", "sinon": "^2.3.1", @@ -14252,10 +14252,11 @@ } }, "node_modules/rollup": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", - "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -14263,7 +14264,7 @@ "node": ">=10.0.0" }, "optionalDependencies": { - "fsevents": "~2.1.2" + "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-terser": { @@ -14390,21 +14391,6 @@ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", "dev": true }, - "node_modules/rollup/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 525302c6a..3de7bad9e 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "nyc": "^15.0.1", "prettier": "^1.19.1", "promise-polyfill": "8.1.0", - "rollup": "2.2.0", + "rollup": "2.79.2", "rollup-plugin-terser": "^5.3.0", "rollup-plugin-typescript2": "^0.27.1", "sinon": "^2.3.1", From 0237be2b3448c518a9de0de0e1ac2ebc081e23a7 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 25 Apr 2025 15:00:49 +0600 Subject: [PATCH 149/200] Bump body-parser from 1.20.2 to 1.20.3 (#1037) Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.2 to 1.20.3. - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3) --- updated-dependencies: - dependency-name: body-parser dependency-version: 1.20.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 305 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 246 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8393d04a9..228e10949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6564,10 +6564,11 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -6577,7 +6578,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -6614,21 +6615,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6880,14 +6866,32 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/call-bind": { + "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8041,6 +8045,21 @@ "void-elements": "^2.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -8199,6 +8218,26 @@ "node": ">= 0.8" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -8206,6 +8245,19 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -9095,10 +9147,14 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -9128,15 +9184,25 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9151,6 +9217,20 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -9247,6 +9327,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -9293,23 +9386,12 @@ "node": ">=8" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9342,6 +9424,19 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -11755,6 +11850,16 @@ "dev": true, "peer": true }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -13054,10 +13159,14 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13714,6 +13823,22 @@ "node": ">=0.9" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/querystring": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", @@ -14658,14 +14783,76 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" From a62fdc61a87b8df803f7515e737a8cd1fa5acef4 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 2 May 2025 19:40:45 +0600 Subject: [PATCH 150/200] [FSSDK-11473] test-ci removal from pipeline (#1038) --- .github/workflows/javascript.yml | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 6f7143b62..30c0bf66e 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -40,24 +40,24 @@ jobs: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} - crossbrowser_and_umd_unit_tests: - runs-on: ubuntu-latest - env: - BROWSER_STACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - BROWSER_STACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - steps: - - uses: actions/checkout@v3 - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: ./package-lock.json - - name: Cross-browser and umd unit tests - working-directory: . - run: | - npm install - npm run test-ci + # crossbrowser_and_umd_unit_tests: + # runs-on: ubuntu-latest + # env: + # BROWSER_STACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + # BROWSER_STACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + # steps: + # - uses: actions/checkout@v3 + # - name: Set up Node + # uses: actions/setup-node@v3 + # with: + # node-version: 16 + # cache: 'npm' + # cache-dependency-path: ./package-lock.json + # - name: Cross-browser and umd unit tests + # working-directory: . + # run: | + # npm install + # npm run test-ci unit_tests: runs-on: ubuntu-latest From dd66beab89a60b9e81189b4e8f29747cb849b0b5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 5 May 2025 19:37:37 +0600 Subject: [PATCH 151/200] [FSSDK-11492] make public promise rejection messages non tree-shakable (#1041) --- lib/event_processor/batch_event_processor.ts | 14 +++++++---- .../forwarding_event_processor.ts | 8 +++--- lib/message/error_message.ts | 14 ++--------- lib/odp/event_manager/odp_event_manager.ts | 18 +++++++------ lib/odp/odp_manager.ts | 8 +++--- lib/optimizely/index.tests.js | 8 +++--- lib/optimizely/index.ts | 20 +++++++++------ .../polling_datafile_manager.ts | 12 ++++++--- .../project_config_manager.spec.ts | 8 +++--- lib/project_config/project_config_manager.ts | 25 +++++++++++++------ lib/service.ts | 4 ++- 11 files changed, 81 insertions(+), 58 deletions(-) diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 40282a8f3..baf7a2d86 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -27,8 +27,10 @@ import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; import { EventEmitter } from "../utils/event_emitter/event_emitter"; import { IdGenerator } from "../utils/id_generator"; import { areEventContextsEqual } from "./event_builder/user_event"; -import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "error_message"; +import { FAILED_TO_DISPATCH_EVENTS, SERVICE_NOT_RUNNING } from "error_message"; import { OptimizelyError } from "../error/optimizly_error"; +import { sprintf } from "../utils/fns"; +import { SERVICE_STOPPED_BEFORE_RUNNING } from "../service"; export const DEFAULT_MIN_BACKOFF = 1000; export const DEFAULT_MAX_BACKOFF = 32000; @@ -174,7 +176,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { const dispatcher = closing && this.closingEventDispatcher ? this.closingEventDispatcher : this.eventDispatcher; return dispatcher.dispatchEvent(request).then((res) => { if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode)); + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS, res.statusCode)); } return Promise.resolve(res); }); @@ -209,7 +211,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { }).catch((err) => { // if the dispatch fails, the events will still be // in the store for future processing - this.logger?.error(FAILED_TO_DISPATCH_EVENTS, err); + this.logger?.error(err); }).finally(() => { this.runningTask.delete(taskId); ids.forEach((id) => this.dispatchingEventIds.delete(id)); @@ -228,7 +230,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { async process(event: ProcessableEvent): Promise<void> { if (!this.isRunning()) { - return Promise.reject('Event processor is not running'); + return Promise.reject(new OptimizelyError(SERVICE_NOT_RUNNING, 'BatchEventProcessor')); } const eventWithId = { @@ -285,7 +287,9 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new OptimizelyError(EVENT_PROCESSOR_STOPPED)); + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'BatchEventProcessor') + )); } this.state = ServiceState.Stopping; diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 744ac5975..a0587ab6a 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -23,8 +23,8 @@ import { buildLogEvent } from './event_builder/log_event'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; -import { SERVICE_STOPPED_BEFORE_RUNNING } from 'error_message'; -import { OptimizelyError } from '../error/optimizly_error'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; +import { sprintf } from '../utils/fns'; class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; @@ -57,7 +57,9 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new OptimizelyError(SERVICE_STOPPED_BEFORE_RUNNING)); + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'ForwardingEventProcessor')) + ); } this.state = ServiceState.Terminated; diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index d820f59ee..b47e718bf 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -41,7 +41,6 @@ export const NO_EVENT_PROCESSOR = 'No event processor is provided'; export const NO_VARIATION_FOR_EXPERIMENT_KEY = 'No variation key %s defined in datafile for experiment %s.'; export const ODP_CONFIG_NOT_AVAILABLE = 'ODP config is not available.'; export const ODP_EVENT_FAILED = 'ODP event send failed.'; -export const ODP_EVENT_MANAGER_IS_NOT_RUNNING = 'ODP event manager is not running.'; export const ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE = 'ODP events should have at least one key-value pair in identifiers.'; export const ODP_EVENT_FAILED_ODP_MANAGER_MISSING = 'ODP Event failed to send. (ODP Manager not available).'; export const ODP_NOT_INTEGRATED = 'ODP is not integrated'; @@ -89,25 +88,16 @@ export const REQUEST_TIMEOUT = 'Request timeout'; export const REQUEST_ERROR = 'Request error'; export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; -export const ONREADY_TIMEOUT = 'onReady timeout expired after %s ms'; -export const INSTANCE_CLOSED = 'Instance closed'; -export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it could be started'; -export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; -export const NO_SDKKEY_OR_DATAFILE = 'At least one of sdkKey or datafile must be provided'; export const RETRY_CANCELLED = 'Retry cancelled'; -export const SERVICE_STOPPED_BEFORE_RUNNING = 'Service stopped before running'; export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; export const SEND_BEACON_FAILED = 'sendBeacon failed'; -export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' -export const FAILED_TO_DISPATCH_EVENTS_WITH_ARG = 'Failed to dispatch events: %s'; -export const EVENT_PROCESSOR_STOPPED = 'Event processor stopped before it could be started'; -export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before running'; +export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events, status: %s'; export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; -export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to page unload event: "%s"'; export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; export const CMAB_FETCH_FAILED = 'CMAB decision fetch failed with status: %s'; export const INVALID_CMAB_FETCH_RESPONSE = 'Invalid CMAB fetch response'; export const PROMISE_NOT_ALLOWED = "Promise value is not allowed in sync operation"; +export const SERVICE_NOT_RUNNING = "%s not running"; export const messages: string[] = []; diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index a076655b5..3a9c591cc 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -27,14 +27,16 @@ import { EVENT_ACTION_INVALID, EVENT_DATA_INVALID, FAILED_TO_SEND_ODP_EVENTS, - ODP_EVENT_MANAGER_IS_NOT_RUNNING, ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE, ODP_NOT_INTEGRATED, - FAILED_TO_DISPATCH_EVENTS_WITH_ARG, - ODP_EVENT_MANAGER_STOPPED + FAILED_TO_DISPATCH_EVENTS, + ODP_EVENT_MANAGER_STOPPED, + SERVICE_NOT_RUNNING } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; import { LoggerFacade } from '../../logging/logger'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../../service'; +import { sprintf } from '../../utils/fns'; export interface OdpEventManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; @@ -86,7 +88,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag private async executeDispatch(odpConfig: OdpConfig, batch: OdpEvent[]): Promise<unknown> { const res = await this.apiManager.sendEvents(odpConfig, batch); if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode)); + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS, res.statusCode)); } return await Promise.resolve(res); } @@ -113,7 +115,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } start(): void { - if (!this.isNew) { + if (!this.isNew()) { return; } @@ -164,7 +166,9 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } if (this.isNew()) { - this.startPromise.reject(new OptimizelyError(ODP_EVENT_MANAGER_STOPPED)); + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'OdpEventManager') + )); } this.flush(); @@ -174,7 +178,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag sendEvent(event: OdpEvent): void { if (!this.isRunning()) { - this.logger?.error(ODP_EVENT_MANAGER_IS_NOT_RUNNING); + this.logger?.error(SERVICE_NOT_RUNNING, 'OdpEventManager'); return; } diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 68a2b2c79..8baaed658 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -29,8 +29,8 @@ import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; import { ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION, ODP_USER_KEY } from './constant'; import { isVuid } from '../vuid/vuid'; import { Maybe } from '../utils/type'; -import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from 'error_message'; -import { OptimizelyError } from '../error/optimizly_error'; +import { sprintf } from '../utils/fns'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; export interface OdpManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean; @@ -151,7 +151,9 @@ export class DefaultOdpManager extends BaseService implements OdpManager { } if (!this.isRunning()) { - this.startPromise.reject(new OptimizelyError(ODP_MANAGER_STOPPED_BEFORE_RUNNING)); + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'OdpManager') + )); } this.state = ServiceState.Stopping; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 5ec683a92..77ce8e0f1 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -44,10 +44,10 @@ import { NOT_TRACKING_USER, EVENT_KEY_NOT_FOUND, INVALID_EXPERIMENT_KEY, - ONREADY_TIMEOUT, SERVICE_STOPPED_BEFORE_RUNNING } from 'error_message'; +import { ONREADY_TIMEOUT, INSTANCE_CLOSED } from './'; import { AUDIENCE_EVALUATION_RESULT_COMBINED, USER_NOT_IN_EXPERIMENT, @@ -9455,8 +9455,7 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); }, (err) => { - assert.equal(err.baseMessage, ONREADY_TIMEOUT); - assert.deepEqual(err.params, [ 500 ]); + assert.equal(err.message, sprintf(ONREADY_TIMEOUT, 500)); }); }); @@ -9479,8 +9478,7 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); }, (err) => { - assert.equal(err.baseMessage, ONREADY_TIMEOUT); - assert.deepEqual(err.params, [ 30000 ]); + assert.equal(err.message, sprintf(ONREADY_TIMEOUT, 30000)); }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 6895fcea7..09b7d47d9 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -59,7 +59,7 @@ import { NODE_CLIENT_ENGINE, CLIENT_VERSION, } from '../utils/enums'; -import { Fn, Maybe, OpType, OpValue } from '../utils/type'; +import { Fn, Maybe, OpType } from '../utils/type'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; @@ -75,9 +75,6 @@ import { EVENT_KEY_NOT_FOUND, NOT_TRACKING_USER, VARIABLE_REQUESTED_WITH_WRONG_TYPE, - ONREADY_TIMEOUT, - INSTANCE_CLOSED, - SERVICE_STOPPED_BEFORE_RUNNING } from 'error_message'; import { @@ -98,6 +95,8 @@ import { VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, } from 'log_message'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; + import { ErrorNotifier } from '../error/error_notifier'; import { ErrorReporter } from '../error/error_reporter'; import { OptimizelyError } from '../error/optimizly_error'; @@ -113,6 +112,9 @@ type StringInputs = Partial<Record<InputKey, unknown>>; type DecisionReasons = (string | number)[]; +export const INSTANCE_CLOSED = 'Instance closed'; +export const ONREADY_TIMEOUT = 'onReady timeout expired after %s ms'; + /** * options required to create optimizely object */ @@ -1257,7 +1259,9 @@ export default class Optimizely extends BaseService implements Client { } if (!this.isRunning()) { - this.startPromise.reject(new OptimizelyError(SERVICE_STOPPED_BEFORE_RUNNING)); + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'Client') + )); } this.state = ServiceState.Stopping; @@ -1322,14 +1326,16 @@ export default class Optimizely extends BaseService implements Client { const onReadyTimeout = () => { this.cleanupTasks.delete(cleanupTaskId); - timeoutPromise.reject(new OptimizelyError(ONREADY_TIMEOUT, timeoutValue)); + timeoutPromise.reject(new Error( + sprintf(ONREADY_TIMEOUT, timeoutValue) + )); }; const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); this.cleanupTasks.set(cleanupTaskId, () => { clearTimeout(readyTimeout); - timeoutPromise.reject(new OptimizelyError(INSTANCE_CLOSED)); + timeoutPromise.reject(new Error(INSTANCE_CLOSED)); }); return Promise.race([this.onRunning().then(() => { diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index bac6adc1e..fbbbeb0e0 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -24,10 +24,8 @@ import { Repeater } from '../utils/repeater/repeater'; import { Consumer, Fn } from '../utils/type'; import { isSuccessStatusCode } from '../utils/http_request_handler/http_util'; import { - DATAFILE_MANAGER_STOPPED, DATAFILE_FETCH_REQUEST_FAILED, ERROR_FETCHING_DATAFILE, - FAILED_TO_FETCH_DATAFILE, } from 'error_message'; import { ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN, @@ -40,6 +38,10 @@ import { LoggerFacade } from '../logging/logger'; export const LOGGER_NAME = 'PollingDatafileManager'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; + +export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; + export class PollingDatafileManager extends BaseService implements DatafileManager { private requestHandler: RequestHandler; private currentDatafile?: string; @@ -123,7 +125,9 @@ export class PollingDatafileManager extends BaseService implements DatafileManag } if (this.isNew() || this.isStarting()) { - this.startPromise.reject(new OptimizelyError(DATAFILE_MANAGER_STOPPED)); + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'PollingDatafileManager') + )); } this.state = ServiceState.Terminated; @@ -136,7 +140,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag private handleInitFailure(): void { this.state = ServiceState.Failed; this.repeater.stop(); - const error = new OptimizelyError(FAILED_TO_FETCH_DATAFILE); + const error = new Error(FAILED_TO_FETCH_DATAFILE); this.startPromise.reject(error); this.stopPromise.reject(error); } diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts index 3e3236644..a8c98ece4 100644 --- a/lib/project_config/project_config_manager.spec.ts +++ b/lib/project_config/project_config_manager.spec.ts @@ -209,10 +209,10 @@ describe('ProjectConfigManagerImpl', () => { describe('when datafile is invalid', () => { it('should reject onRunning() with the same error if datafileManager.onRunning() rejects', async () => { - const datafileManager = getMockDatafileManager({ onRunning: Promise.reject('test error') }); + const datafileManager = getMockDatafileManager({ onRunning: Promise.reject(new Error('test error')) }); const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); manager.start(); - await expect(manager.onRunning()).rejects.toBe('test error'); + await expect(manager.onRunning()).rejects.toThrow('DatafileManager failed to start, reason: test error'); }); it('should resolve onRunning() if datafileManager.onUpdate() is fired and should update config', async () => { @@ -258,10 +258,10 @@ describe('ProjectConfigManagerImpl', () => { describe('when datafile is not provided', () => { it('should reject onRunning() if datafileManager.onRunning() rejects', async () => { - const datafileManager = getMockDatafileManager({ onRunning: Promise.reject('test error') }); + const datafileManager = getMockDatafileManager({ onRunning: Promise.reject(new Error('test error')) }); const manager = new ProjectConfigManagerImpl({ datafileManager }); manager.start(); - await expect(manager.onRunning()).rejects.toBe('test error'); + await expect(manager.onRunning()).rejects.toThrow('DatafileManager failed to start, reason: test error'); }); it('should reject onRunning() and onTerminated if datafileManager emits an invalid datafile in the first onUpdate', async () => { diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index 95e3fa029..edf88a174 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -22,9 +22,16 @@ import { scheduleMicrotask } from '../utils/microtask'; import { Service, ServiceState, BaseService } from '../service'; import { Consumer, Fn, Transformer } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; -import { DATAFILE_MANAGER_STOPPED, NO_SDKKEY_OR_DATAFILE, DATAFILE_MANAGER_FAILED_TO_START } from 'error_message'; -import { OptimizelyError } from '../error/optimizly_error'; +import { + SERVICE_FAILED_TO_START, + SERVICE_STOPPED_BEFORE_RUNNING, +} from '../service' + +export const NO_SDKKEY_OR_DATAFILE = 'sdkKey or datafile must be provided'; +export const GOT_INVALID_DATAFILE = 'got invalid datafile'; + +import { sprintf } from '../utils/fns'; interface ProjectConfigManagerConfig { datafile?: string | Record<string, unknown>; jsonSchemaValidator?: Transformer<unknown, boolean>, @@ -82,7 +89,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf this.state = ServiceState.Starting; if (!this.datafile && !this.datafileManager) { - this.handleInitError(new OptimizelyError(NO_SDKKEY_OR_DATAFILE)); + this.handleInitError(new Error(NO_SDKKEY_OR_DATAFILE)); return; } @@ -119,14 +126,16 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf } private handleDatafileManagerError(err: Error): void { - this.logger?.error(DATAFILE_MANAGER_FAILED_TO_START, err); + this.logger?.error(SERVICE_FAILED_TO_START, 'DatafileManager', err.message); // If datafile manager onRunning() promise is rejected, and the project config manager // is still in starting state, that means a datafile was not provided in cofig or was invalid, // otherwise the state would have already been set to running synchronously. // In this case, we cannot recover. if (this.isStarting()) { - this.handleInitError(err); + this.handleInitError(new Error( + sprintf(SERVICE_FAILED_TO_START, 'DatafileManager', err.message) + )); } } @@ -173,7 +182,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf const fatalError = (this.isStarting() && !this.datafileManager) || (this.isStarting() && !fromConfig); if (fatalError) { - this.handleInitError(err); + this.handleInitError(new Error(GOT_INVALID_DATAFILE)); } } } @@ -206,7 +215,9 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf } if (this.isNew() || this.isStarting()) { - this.startPromise.reject(new OptimizelyError(DATAFILE_MANAGER_STOPPED)); + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'ProjectConfigManager') + )); } this.state = ServiceState.Stopping; diff --git a/lib/service.ts b/lib/service.ts index b024ef510..3022aa806 100644 --- a/lib/service.ts +++ b/lib/service.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024 Optimizely + * Copyright 2024-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ import { LoggerFacade, LogLevel, LogLevelToLower } from './logging/logger' import { resolvablePromise, ResolvablePromise } from "./utils/promise/resolvablePromise"; +export const SERVICE_FAILED_TO_START = '%s failed to start, reason: %s'; +export const SERVICE_STOPPED_BEFORE_RUNNING = '%s stopped before running'; /** * The service interface represents an object with an operational state, From 32f857e24643bd21055e848a45c2ca8d6f28c39a Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 6 May 2025 18:16:30 +0600 Subject: [PATCH 152/200] [FSSDK-11197] EventTags type fix (#1039) --- .../event_builder/log_event.ts | 8 ++------ .../event_builder/user_event.ts | 6 +----- lib/optimizely/index.ts | 5 +++-- lib/shared_types.ts | 5 ++++- lib/utils/event_tag_utils/index.ts | 20 ++++++++++--------- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index 6266d8a5a..c4132567e 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -13,14 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - EventTags, - ConversionEvent, - ImpressionEvent, - UserEvent, -} from './user_event'; +import { ConversionEvent, ImpressionEvent, UserEvent } from './user_event'; import { LogEvent } from '../event_dispatcher/event_dispatcher'; +import { EventTags } from '../../shared_types'; const ACTIVATE_EVENT_KEY = 'campaign_activated' const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' diff --git a/lib/event_processor/event_builder/user_event.ts b/lib/event_processor/event_builder/user_event.ts index e2e52bedc..e0a91b5ae 100644 --- a/lib/event_processor/event_builder/user_event.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -25,7 +25,7 @@ import { ProjectConfig, } from '../../project_config/project_config'; -import { UserAttributes } from '../../shared_types'; +import { EventTags, UserAttributes } from '../../shared_types'; import { LoggerFacade } from '../../logging/logger'; export type VisitorAttribute = { @@ -79,10 +79,6 @@ export type ImpressionEvent = BaseUserEvent & { cmabUuid?: string; }; -export type EventTags = { - [key: string]: string | number | null; -}; - export type ConversionEvent = BaseUserEvent & { type: 'conversion'; diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 09b7d47d9..883391e4a 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -609,8 +609,9 @@ export default class Optimizely extends BaseService implements Client { */ private filterEmptyValues(map: EventTags | undefined): EventTags | undefined { for (const key in map) { - if (map.hasOwnProperty(key) && (map[key] === null || map[key] === undefined)) { - delete map[key]; + const typedKey = key as keyof EventTags; + if (map.hasOwnProperty(typedKey) && (map[typedKey] === null || map[typedKey] === undefined)) { + delete map[typedKey]; } } return map; diff --git a/lib/shared_types.ts b/lib/shared_types.ts index c203613a3..4a727af74 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -89,7 +89,10 @@ export interface UserProfile { } export type EventTags = { - [key: string]: string | number | null; + revenue?: string | number | null; + value?: string | number | null; + $opt_event_properties?: Record<string, unknown>; + [key: string]: unknown; }; export interface UserProfileService { diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index 7c4377d76..d50292a39 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -19,12 +19,10 @@ import { PARSED_NUMERIC_VALUE, PARSED_REVENUE_VALUE, } from 'log_message'; -import { EventTags } from '../../event_processor/event_builder/user_event'; import { LoggerFacade } from '../../logging/logger'; -import { - RESERVED_EVENT_KEYWORDS, -} from '../enums'; +import { RESERVED_EVENT_KEYWORDS } from '../enums'; +import { EventTags } from '../../shared_types'; /** * Provides utility method for parsing event tag values @@ -41,16 +39,18 @@ const VALUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.VALUE; export function getRevenueValue(eventTags: EventTags, logger?: LoggerFacade): number | null { const rawValue = eventTags[REVENUE_EVENT_METRIC_NAME]; - if (rawValue == null) { // null or undefined event values + if (rawValue == null) { + // null or undefined event values return null; } - const parsedRevenueValue = typeof rawValue === 'string' ? parseInt(rawValue) : rawValue; + const parsedRevenueValue = typeof rawValue === 'string' ? parseInt(rawValue) : Math.trunc(rawValue); if (isFinite(parsedRevenueValue)) { logger?.info(PARSED_REVENUE_VALUE, parsedRevenueValue); return parsedRevenueValue; - } else { // NaN, +/- infinity values + } else { + // NaN, +/- infinity values logger?.info(FAILED_TO_PARSE_REVENUE, rawValue); return null; } @@ -65,7 +65,8 @@ export function getRevenueValue(eventTags: EventTags, logger?: LoggerFacade): nu export function getEventValue(eventTags: EventTags, logger?: LoggerFacade): number | null { const rawValue = eventTags[VALUE_EVENT_METRIC_NAME]; - if (rawValue == null) { // null or undefined event values + if (rawValue == null) { + // null or undefined event values return null; } @@ -74,7 +75,8 @@ export function getEventValue(eventTags: EventTags, logger?: LoggerFacade): numb if (isFinite(parsedEventValue)) { logger?.info(PARSED_NUMERIC_VALUE, parsedEventValue); return parsedEventValue; - } else { // NaN, +/- infinity values + } else { + // NaN, +/- infinity values logger?.info(FAILED_TO_PARSE_VALUE, rawValue); return null; } From 299e1d7834f579b5dff7e4630b7cd59205b06429 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 7 May 2025 01:47:13 +0600 Subject: [PATCH 153/200] [FSSDK-11494] fix UserAttributes type (#1042) --- lib/core/decision_service/index.spec.ts | 12 +++--- .../event_builder/log_event.ts | 7 ++-- .../event_builder/user_event.ts | 37 +++++++++++-------- lib/optimizely_user_context/index.tests.js | 11 +++--- lib/optimizely_user_context/index.ts | 9 +++-- lib/shared_types.ts | 4 +- lib/utils/enums/index.ts | 1 - 7 files changed, 46 insertions(+), 35 deletions(-) diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts index 8ddc736eb..975653611 100644 --- a/lib/core/decision_service/index.spec.ts +++ b/lib/core/decision_service/index.spec.ts @@ -20,7 +20,7 @@ import OptimizelyUserContext from '../../optimizely_user_context'; import { bucket } from '../bucketer'; import { getTestProjectConfig, getTestProjectConfigWithFeatures } from '../../tests/test_data'; import { createProjectConfig, ProjectConfig } from '../../project_config/project_config'; -import { BucketerParams, Experiment, OptimizelyDecideOption, UserProfile } from '../../shared_types'; +import { BucketerParams, Experiment, OptimizelyDecideOption, UserAttributes, UserProfile } from '../../shared_types'; import { CONTROL_ATTRIBUTES, DECISION_SOURCES } from '../../utils/enums'; import { getDecisionTestDatafile } from '../../tests/decision_test_datafile'; import { Value } from '../../utils/promise/operation_value'; @@ -344,7 +344,7 @@ describe('DecisionService', () => { const config = createProjectConfig(cloneDeep(testData)); const experiment = config.experimentIdMap['111127']; - const attributes: any = { + const attributes: UserAttributes = { $opt_experiment_bucket_map: { '111127': { variation_id: '111129', // ID of the 'variation' variation @@ -682,7 +682,7 @@ describe('DecisionService', () => { const config = createProjectConfig(cloneDeep(testData)); const experiment = config.experimentIdMap['111127']; - const attributes: any = { + const attributes: UserAttributes = { $opt_experiment_bucket_map: { '111127': { variation_id: '111129', // ID of the 'variation' variation @@ -715,7 +715,7 @@ describe('DecisionService', () => { const config = createProjectConfig(cloneDeep(testData)); const experiment = config.experimentIdMap['111127']; - const attributes: any = { + const attributes: UserAttributes = { $opt_experiment_bucket_map: { '122227': { variation_id: '111129', // ID of the 'variation' variation @@ -748,7 +748,7 @@ describe('DecisionService', () => { const config = createProjectConfig(cloneDeep(testData)); const experiment = config.experimentIdMap['111127']; - const attributes: any = { + const attributes: UserAttributes = { $opt_experiment_bucket_map: { '111127': { variation_id: '111129', // ID of the 'variation' variation @@ -774,7 +774,7 @@ describe('DecisionService', () => { const config = createProjectConfig(cloneDeep(testData)); const experiment = config.experimentIdMap['111127']; - const attributes: any = { + const attributes: UserAttributes = { $opt_experiment_bucket_map: { '111127': { variation_id: '111129', // ID of the 'variation' variation diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index c4132567e..8e65d6ba1 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -15,12 +15,13 @@ */ import { ConversionEvent, ImpressionEvent, UserEvent } from './user_event'; +import { CONTROL_ATTRIBUTES } from '../../utils/enums'; + import { LogEvent } from '../event_dispatcher/event_dispatcher'; import { EventTags } from '../../shared_types'; const ACTIVATE_EVENT_KEY = 'campaign_activated' const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' -const BOT_FILTERING_KEY = '$opt_bot_filtering' export type EventBatch = { account_id: string @@ -204,8 +205,8 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { if (typeof data.context.botFiltering === 'boolean') { visitor.attributes.push({ - entity_id: BOT_FILTERING_KEY, - key: BOT_FILTERING_KEY, + entity_id: CONTROL_ATTRIBUTES.BOT_FILTERING, + key: CONTROL_ATTRIBUTES.BOT_FILTERING, type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, value: data.context.botFiltering, }) diff --git a/lib/event_processor/event_builder/user_event.ts b/lib/event_processor/event_builder/user_event.ts index e0a91b5ae..c6c6c5446 100644 --- a/lib/event_processor/event_builder/user_event.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -254,23 +254,30 @@ const buildVisitorAttributes = ( attributes?: UserAttributes, logger?: LoggerFacade ): VisitorAttribute[] => { - const builtAttributes: VisitorAttribute[] = []; + if (!attributes) { + return []; + } + // Omit attribute values that are not supported by the log endpoint. - if (attributes) { - Object.keys(attributes || {}).forEach(function(attributeKey) { - const attributeValue = attributes[attributeKey]; - if (isAttributeValid(attributeKey, attributeValue)) { - const attributeId = getAttributeId(configObj, attributeKey, logger); - if (attributeId) { - builtAttributes.push({ - entityId: attributeId, - key: attributeKey, - value: attributeValue!, - }); - } + const builtAttributes: VisitorAttribute[] = []; + Object.keys(attributes).forEach(function(attributeKey) { + const attributeValue = attributes[attributeKey]; + + if (typeof attributeValue === 'object' || typeof attributeValue === 'undefined') { + return; + } + + if (isAttributeValid(attributeKey, attributeValue)) { + const attributeId = getAttributeId(configObj, attributeKey, logger); + if (attributeId) { + builtAttributes.push({ + entityId: attributeId, + key: attributeKey, + value: attributeValue, + }); } - }); - } + } + }); return builtAttributes; } diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index d8f4cdf09..1ca29ef1a 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -20,12 +20,13 @@ import { NOTIFICATION_TYPES } from '../notification_center/type'; import OptimizelyUserContext from './'; import { createNotificationCenter } from '../notification_center'; import Optimizely from '../optimizely'; -import { CONTROL_ATTRIBUTES, LOG_LEVEL } from '../utils/enums'; +import { LOG_LEVEL } from '../utils/enums'; import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; +import { FORCED_DECISION_NULL_RULE_KEY } from './index' import { USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, @@ -449,7 +450,7 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(decision.userContext.getAttributes(), {}); assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); assert.deepEqual( - decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], { variationKey } ); assert.equal( @@ -475,7 +476,7 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(decision.userContext.getAttributes(), {}); assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); assert.deepEqual( - decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], { variationKey } ); assert.equal( @@ -509,7 +510,7 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(decision.userContext.getAttributes(), {}); assert.deepEqual(Object.values(decision.userContext.forcedDecisionsMap).length, 1); assert.deepEqual( - decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], { variationKey } ); assert.equal( @@ -776,7 +777,7 @@ describe('lib/optimizely_user_context', function() { assert.equal(decision.ruleKey, '18322080788'); assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); assert.deepEqual( - decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], { variationKey } ); assert.equal( diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index 46fa103f4..75259feb8 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -23,9 +23,10 @@ import { UserAttributeValue, UserAttributes, } from '../shared_types'; -import { CONTROL_ATTRIBUTES } from '../utils/enums'; import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; +export const FORCED_DECISION_NULL_RULE_KEY = '$opt_null_rule_key'; + interface OptimizelyUserContextConfig { optimizely: Optimizely; userId: string; @@ -142,7 +143,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean { const flagKey = context.flagKey; - const ruleKey = context.ruleKey ?? CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY; + const ruleKey = context.ruleKey ?? FORCED_DECISION_NULL_RULE_KEY; const variationKey = decision.variationKey; const forcedDecision = { variationKey }; @@ -169,7 +170,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { * @return {boolean} true if the forced decision has been removed successfully */ removeForcedDecision(context: OptimizelyDecisionContext): boolean { - const ruleKey = context.ruleKey ?? CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY; + const ruleKey = context.ruleKey ?? FORCED_DECISION_NULL_RULE_KEY; const flagKey = context.flagKey; let isForcedDecisionRemoved = false; @@ -204,7 +205,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { */ private findForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null { let variationKey; - const validRuleKey = context.ruleKey ?? CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY; + const validRuleKey = context.ruleKey ?? FORCED_DECISION_NULL_RULE_KEY; const flagKey = context.flagKey; if (this.forcedDecisionsMap.hasOwnProperty(context.flagKey)) { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 4a727af74..0a1582e4a 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -72,9 +72,11 @@ export interface DecisionResponse<T> { readonly reasons: [string, ...any[]][]; } -export type UserAttributeValue = string | number | boolean | null; +export type UserAttributeValue = string | number | boolean | null | undefined | ExperimentBucketMap; export type UserAttributes = { + $opt_bucketing_id?: string; + $opt_experiment_bucket_map?: ExperimentBucketMap; [name: string]: UserAttributeValue; }; diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index fe4fe9fbe..9d1fea0d3 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -36,7 +36,6 @@ export const CONTROL_ATTRIBUTES = { BUCKETING_ID: '$opt_bucketing_id', STICKY_BUCKETING_KEY: '$opt_experiment_bucket_map', USER_AGENT: '$opt_user_agent', - FORCED_DECISION_NULL_RULE_KEY: '$opt_null_rule_key', }; export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; From 379ebd0222e3561f25b61868bc73476449aceaf1 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 7 May 2025 02:54:40 +0600 Subject: [PATCH 154/200] [FSSDK-11497] fire config update events synchronously (#1043) fire the project config updates synchronously. this will allow the optimizely class to propagate the initial project config synchronously to all sub components. This can be useful when the client is initialized with a datafile. One downside is, the update event handler needs to be registered before calling start() on the config manager, but since we only use the config manager internally, we can maintain that. --- .../project_config_manager.spec.ts | 34 +++++++++---------- lib/project_config/project_config_manager.ts | 5 +-- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts index a8c98ece4..76c9af736 100644 --- a/lib/project_config/project_config_manager.spec.ts +++ b/lib/project_config/project_config_manager.spec.ts @@ -121,24 +121,17 @@ describe('ProjectConfigManagerImpl', () => { expect(manager.getState()).toBe(ServiceState.Running); }); - it('should call onUpdate listeners registered before or after start() with the project config after resolving onRunning()', async () => { + it('should call onUpdate listeners registered before start() with the project config', async () => { const logger = getMockLogger(); const manager = new ProjectConfigManagerImpl({ logger, datafile: testData.getTestProjectConfig()}); - const listener1 = vi.fn(); - manager.onUpdate(listener1); + const listener = vi.fn(); + manager.onUpdate(listener); manager.start(); - const listener2 = vi.fn(); - manager.onUpdate(listener2); - expect(listener1).not.toHaveBeenCalled(); - expect(listener2).not.toHaveBeenCalledOnce(); await manager.onRunning(); - expect(listener1).toHaveBeenCalledOnce(); - expect(listener2).toHaveBeenCalledOnce(); - - expect(listener1).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); - expect(listener2).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + expect(listener).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); }); it('should return the correct config from getConfig() both before or after onRunning() resolves', async () => { @@ -187,8 +180,8 @@ describe('ProjectConfigManagerImpl', () => { const listener = vi.fn(); const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); - manager.start(); manager.onUpdate(listener); + manager.start(); await expect(manager.onRunning()).resolves.not.toThrow(); expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); }); @@ -309,11 +302,12 @@ describe('ProjectConfigManagerImpl', () => { const datafile = testData.getTestProjectConfig(); const manager = new ProjectConfigManagerImpl({ datafile, datafileManager }); - manager.start(); const listener = vi.fn(); manager.onUpdate(listener); + manager.start(); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); await manager.onRunning(); expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); @@ -334,11 +328,12 @@ describe('ProjectConfigManagerImpl', () => { const logger = getMockLogger(); const datafile = testData.getTestProjectConfig(); const manager = new ProjectConfigManagerImpl({ logger, datafile, datafileManager }); - manager.start(); const listener = vi.fn(); manager.onUpdate(listener); + manager.start(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); await manager.onRunning(); expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); @@ -379,11 +374,12 @@ describe('ProjectConfigManagerImpl', () => { const datafile = testData.getTestProjectConfig(); const manager = new ProjectConfigManagerImpl({ datafile, datafileManager }); - manager.start(); const listener = vi.fn(); manager.onUpdate(listener); + manager.start(); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); await manager.onRunning(); expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); @@ -401,11 +397,12 @@ describe('ProjectConfigManagerImpl', () => { const datafileManager = getMockDatafileManager({}); const manager = new ProjectConfigManagerImpl({ datafile }); - manager.start(); const listener = vi.fn(); const dispose = manager.onUpdate(listener); + manager.start(); + await manager.onRunning(); expect(listener).toHaveBeenNthCalledWith(1, createProjectConfig(datafile)); @@ -420,11 +417,12 @@ describe('ProjectConfigManagerImpl', () => { const datafile = testData.getTestProjectConfig(); const manager = new ProjectConfigManagerImpl({ datafile: JSON.stringify(datafile) }); - manager.start(); const listener = vi.fn(); manager.onUpdate(listener); + manager.start(); + await manager.onRunning(); expect(listener).toHaveBeenCalledWith(createProjectConfig(datafile)); expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index edf88a174..8d7002c03 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -18,7 +18,6 @@ import { createOptimizelyConfig } from './optimizely_config'; import { OptimizelyConfig } from '../shared_types'; import { DatafileManager } from './datafile_manager'; import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from './project_config'; -import { scheduleMicrotask } from '../utils/microtask'; import { Service, ServiceState, BaseService } from '../service'; import { Consumer, Fn, Transformer } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; @@ -166,9 +165,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf if (this.projectConfig?.revision !== config.revision) { this.projectConfig = config; this.optimizelyConfig = undefined; - scheduleMicrotask(() => { - this.eventEmitter.emit('update', config); - }) + this.eventEmitter.emit('update', config); } } catch (err) { this.logger?.error(err); From 02fad58482048c8deea6ac3701ff547c33a8988c Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 8 May 2025 00:07:36 +0600 Subject: [PATCH 155/200] odp manager adjustment (#1044) --- lib/odp/odp_manager.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 8baaed658..2f8256c38 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -179,11 +179,7 @@ export class DefaultOdpManager extends BaseService implements OdpManager { } this.odpIntegrationConfig = odpIntegrationConfig; - - if (this.isStarting()) { - this.configPromise.resolve(); - } - + this.configPromise.resolve(); this.segmentManager.updateConfig(odpIntegrationConfig) this.eventManager.updateConfig(odpIntegrationConfig); From 7a75f1355f88bb62e1d165757d4f8543a83f4192 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 12 May 2025 23:38:45 +0600 Subject: [PATCH 156/200] [FSSDK-11500] publish ua_parser as a separate bundle (#1045) --- .../{ua_parser.browser.ts => ua_parser.ts} | 2 - package-lock.json | 23 --------- package.json | 16 +++++-- rollup.config.js | 47 +++++++++++++++++++ 4 files changed, 59 insertions(+), 29 deletions(-) rename lib/odp/ua_parser/{ua_parser.browser.ts => ua_parser.ts} (99%) diff --git a/lib/odp/ua_parser/ua_parser.browser.ts b/lib/odp/ua_parser/ua_parser.ts similarity index 99% rename from lib/odp/ua_parser/ua_parser.browser.ts rename to lib/odp/ua_parser/ua_parser.ts index 522c538be..8622b0ade 100644 --- a/lib/odp/ua_parser/ua_parser.browser.ts +++ b/lib/odp/ua_parser/ua_parser.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { UAParser } from 'ua-parser-js'; import { UserAgentInfo } from './user_agent_info'; import { UserAgentParser } from './user_agent_parser'; @@ -30,4 +29,3 @@ const userAgentParser: UserAgentParser = { export function getUserAgentParser(): UserAgentParser { return userAgentParser; } - diff --git a/package-lock.json b/package-lock.json index 228e10949..331bb0974 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "decompress-response": "^7.0.0", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", - "ua-parser-js": "^1.0.38", "uuid": "^9.0.1" }, "devDependencies": { @@ -15925,28 +15924,6 @@ "node": ">=4.2.0" } }, - "node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "engines": { - "node": "*" - } - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/package.json b/package.json index 3de7bad9e..6aaac97d4 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ }, "./browser": { "types": "./dist/index.d.ts", - "import": "./dist/index.browser.es.min.js", - "require": "./dist/index.browser.min.js" + "import": "./dist/index.browser.es.min.js", + "require": "./dist/index.browser.min.js" }, "./react_native": { "types": "./dist/index.d.ts", @@ -46,6 +46,11 @@ "types": "./dist/index.universal.d.ts", "import": "./dist/index.universal.es.min.js", "require": "./dist/index.universal.min.js" + }, + "./ua_parser": { + "types": "./dist/odp/ua_parser/ua_parser.d.ts", + "import": "./dist/ua_parser.es.min.js", + "require": "./dist/ua_parser.min.js" } }, "scripts": { @@ -89,7 +94,6 @@ "decompress-response": "^7.0.0", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", - "ua-parser-js": "^1.0.38", "uuid": "^9.0.1" }, "devDependencies": { @@ -146,7 +150,8 @@ "@react-native-async-storage/async-storage": "^1.2.0", "@react-native-community/netinfo": "^11.3.2", "fast-text-encoding": "^1.0.6", - "react-native-get-random-values": "^1.11.0" + "react-native-get-random-values": "^1.11.0", + "ua-parser-js": "^1.0.38" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { @@ -160,6 +165,9 @@ }, "fast-text-encoding": { "optional": true + }, + "ua-parser-js": { + "optional": true } }, "publishConfig": { diff --git a/rollup.config.js b/rollup.config.js index 2fc077a83..f7cc6c247 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -93,6 +93,51 @@ const esmBundleFor = (platform, opt) => { } }; +const cjsBundleForUAParser = (opt = {}) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; + + const min = minify ? '.min' : ''; + + return { + plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], + external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})), + input: `lib/odp/ua_parser/ua_parser.ts`, + output: { + exports: 'named', + format: 'cjs', + file: `dist/ua_parser${min}${ext}`, + plugins: minify ? [terser()] : undefined, + sourcemap: true, + }, + }; +}; + +const esmBundleForUAParser = (opt = {}) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; + + const min = minify ? '.min' : ''; + + return { + ...cjsBundleForUAParser(), + output: [ + { + format: 'es', + file: `dist/ua_parser.es${min}${ext}`, + plugins: minify ? [terser()] : undefined, + sourcemap: true, + }, + ], + }; +}; + const umdBundle = { plugins: [ resolve({ browser: true }), @@ -147,6 +192,8 @@ const bundles = { 'esm-react-native-min': esmBundleFor('react_native'), 'esm-universal': esmBundleFor('universal'), 'json-schema': jsonSchemaBundle, + 'cjs-ua-parser-min': cjsBundleForUAParser(), + 'esm-ua-parser-min': esmBundleForUAParser(), umd: umdBundle, }; From ecb9dc5db61758e938de612f31f6e67ab540f1da Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 13 May 2025 22:46:40 +0600 Subject: [PATCH 157/200] [FSSDK-11512] remove eventManager and segmentManager from odp public api (#1048) --- lib/entrypoint.universal.test-d.ts | 6 ++- lib/export_types.ts | 4 ++ lib/index.universal.ts | 14 ++++++- lib/odp/odp_manager_factory.browser.spec.ts | 2 - lib/odp/odp_manager_factory.node.spec.ts | 2 - lib/odp/odp_manager_factory.node.ts | 1 - .../odp_manager_factory.react_native.spec.ts | 2 - lib/odp/odp_manager_factory.spec.ts | 34 +---------------- lib/odp/odp_manager_factory.ts | 6 +-- lib/odp/odp_manager_factory.universal.ts | 38 +++++++++++++++++++ lib/vuid/vuid_manager_factory.browser.ts | 1 - 11 files changed, 62 insertions(+), 48 deletions(-) create mode 100644 lib/odp/odp_manager_factory.universal.ts diff --git a/lib/entrypoint.universal.test-d.ts b/lib/entrypoint.universal.test-d.ts index cde68ae97..0dcc3a671 100644 --- a/lib/entrypoint.universal.test-d.ts +++ b/lib/entrypoint.universal.test-d.ts @@ -49,6 +49,9 @@ import { LogLevel } from './logging/logger'; import { OptimizelyDecideOption } from './shared_types'; import { UniversalConfig } from './index.universal'; +import { OpaqueOdpManager } from './odp/odp_manager_factory'; + +import { UniversalOdpManagerOptions } from './odp/odp_manager_factory.universal'; export type UniversalEntrypoint = { // client factory @@ -63,8 +66,7 @@ export type UniversalEntrypoint = { createForwardingEventProcessor: (eventDispatcher: EventDispatcher) => OpaqueEventProcessor; createBatchEventProcessor: (options: UniversalBatchEventProcessorOptions) => OpaqueEventProcessor; - // TODO: odp manager related exports - // createOdpManager: (options: OdpManagerOptions) => OpaqueOdpManager; + createOdpManager: (options: UniversalOdpManagerOptions) => OpaqueOdpManager; // TODO: vuid manager related exports // createVuidManager: (options: VuidManagerOptions) => OpaqueVuidManager; diff --git a/lib/export_types.ts b/lib/export_types.ts index fba5cde09..42e6778b9 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -39,6 +39,10 @@ export type { OpaqueOdpManager, } from './odp/odp_manager_factory'; +export type { + UserAgentParser, +} from './odp/ua_parser/user_agent_parser'; + // Vuid manager related types export type { VuidManagerOptions, diff --git a/lib/index.universal.ts b/lib/index.universal.ts index 6bd233a32..5cc64f51e 100644 --- a/lib/index.universal.ts +++ b/lib/index.universal.ts @@ -39,8 +39,9 @@ export { createPollingProjectConfigManager } from './project_config/config_manag export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.universal'; -// TODO: decide on universal odp manager factory interface -// export { createOdpManager } from './odp/odp_manager_factory.node'; +export { createOdpManager } from './odp/odp_manager_factory.universal'; + +// TODO: decide on vuid manager API for universal // export { createVuidManager } from './vuid/vuid_manager_factory.node'; export * from './common_exports'; @@ -67,6 +68,15 @@ export type { export type { UniversalBatchEventProcessorOptions } from './event_processor/event_processor_factory.universal'; +// odp manager related types +export type { + UniversalOdpManagerOptions, +} from './odp/odp_manager_factory.universal'; + +export type { + UserAgentParser, +} from './odp/ua_parser/user_agent_parser'; + export type { OpaqueEventProcessor, } from './event_processor/event_processor_factory'; diff --git a/lib/odp/odp_manager_factory.browser.spec.ts b/lib/odp/odp_manager_factory.browser.spec.ts index d8ecc8605..75edcdf3d 100644 --- a/lib/odp/odp_manager_factory.browser.spec.ts +++ b/lib/odp/odp_manager_factory.browser.spec.ts @@ -117,9 +117,7 @@ describe('createOdpManager', () => { segmentsCache: {} as any, segmentsCacheSize: 11, segmentsCacheTimeout: 2025, - segmentManager: {} as any, eventFlushInterval: 2222, - eventManager: {} as any, userAgentParser: {} as any, }; const odpManager = createOdpManager(options); diff --git a/lib/odp/odp_manager_factory.node.spec.ts b/lib/odp/odp_manager_factory.node.spec.ts index f89d6ce94..ac3bcb4ce 100644 --- a/lib/odp/odp_manager_factory.node.spec.ts +++ b/lib/odp/odp_manager_factory.node.spec.ts @@ -117,8 +117,6 @@ describe('createOdpManager', () => { segmentsCache: {} as any, segmentsCacheSize: 11, segmentsCacheTimeout: 2025, - segmentManager: {} as any, - eventManager: {} as any, userAgentParser: {} as any, }; const odpManager = createOdpManager(options); diff --git a/lib/odp/odp_manager_factory.node.ts b/lib/odp/odp_manager_factory.node.ts index e59c657bd..7b8f737a7 100644 --- a/lib/odp/odp_manager_factory.node.ts +++ b/lib/odp/odp_manager_factory.node.ts @@ -16,7 +16,6 @@ import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; -import { OdpManager } from './odp_manager'; import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; export const NODE_DEFAULT_API_TIMEOUT = 10_000; diff --git a/lib/odp/odp_manager_factory.react_native.spec.ts b/lib/odp/odp_manager_factory.react_native.spec.ts index fd703d362..95d7be4fc 100644 --- a/lib/odp/odp_manager_factory.react_native.spec.ts +++ b/lib/odp/odp_manager_factory.react_native.spec.ts @@ -117,8 +117,6 @@ describe('createOdpManager', () => { segmentsCache: {} as any, segmentsCacheSize: 11, segmentsCacheTimeout: 2025, - segmentManager: {} as any, - eventManager: {} as any, userAgentParser: {} as any, }; const odpManager = createOdpManager(options); diff --git a/lib/odp/odp_manager_factory.spec.ts b/lib/odp/odp_manager_factory.spec.ts index 94aa565e5..9815f3085 100644 --- a/lib/odp/odp_manager_factory.spec.ts +++ b/lib/odp/odp_manager_factory.spec.ts @@ -90,22 +90,7 @@ describe('getOdpManager', () => { MockExponentialBackoff.mockClear(); }); - it('should use provided segment manager', () => { - const segmentManager = {} as any; - - const odpManager = getOdpManager({ - segmentManager, - segmentRequestHandler: getMockRequestHandler(), - eventRequestHandler: getMockRequestHandler(), - eventRequestGenerator: vi.fn(), - }); - - expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); - const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; - expect(usedSegmentManager).toBe(segmentManager); - }); - - describe('when no segment manager is provided', () => { + describe('segment manager', () => { it('should create a default segment manager with default api manager using the passed eventRequestHandler', () => { const segmentRequestHandler = getMockRequestHandler(); const odpManager = getOdpManager({ @@ -205,22 +190,7 @@ describe('getOdpManager', () => { }); }); - it('uses provided event manager', () => { - const eventManager = {} as any; - - const odpManager = getOdpManager({ - eventManager, - segmentRequestHandler: getMockRequestHandler(), - eventRequestHandler: getMockRequestHandler(), - eventRequestGenerator: vi.fn(), - }); - - expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); - const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; - expect(usedEventManager).toBe(eventManager); - }); - - describe('when no event manager is provided', () => { + describe('event manager', () => { it('should use a default event manager with default api manager using the passed eventRequestHandler and eventRequestGenerator', () => { const eventRequestHandler = getMockRequestHandler(); const eventRequestGenerator = vi.fn(); diff --git a/lib/odp/odp_manager_factory.ts b/lib/odp/odp_manager_factory.ts index 12d229d4b..91504d5e6 100644 --- a/lib/odp/odp_manager_factory.ts +++ b/lib/odp/odp_manager_factory.ts @@ -45,11 +45,9 @@ export type OdpManagerOptions = { segmentsCacheSize?: number; segmentsCacheTimeout?: number; segmentsApiTimeout?: number; - segmentManager?: OdpSegmentManager; eventFlushInterval?: number; eventBatchSize?: number; eventApiTimeout?: number; - eventManager?: OdpEventManager; userAgentParser?: UserAgentParser; }; @@ -90,8 +88,8 @@ const getDefaultEventManager = (options: OdpManagerFactoryOptions) => { } export const getOdpManager = (options: OdpManagerFactoryOptions): OdpManager => { - const segmentManager = options.segmentManager || getDefaultSegmentManager(options); - const eventManager = options.eventManager || getDefaultEventManager(options); + const segmentManager = getDefaultSegmentManager(options); + const eventManager = getDefaultEventManager(options); return new DefaultOdpManager({ segmentManager, diff --git a/lib/odp/odp_manager_factory.universal.ts b/lib/odp/odp_manager_factory.universal.ts new file mode 100644 index 000000000..9ad2bc250 --- /dev/null +++ b/lib/odp/odp_manager_factory.universal.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from '../utils/http_request_handler/http'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; + +export const DEFAULT_API_TIMEOUT = 10_000; +export const DEFAULT_BATCH_SIZE = 1; +export const DEFAULT_FLUSH_INTERVAL = 1000; + +export type UniversalOdpManagerOptions = OdpManagerOptions & { + requestHandler: RequestHandler; +}; + +export const createOdpManager = (options: UniversalOdpManagerOptions): OpaqueOdpManager => { + return getOpaqueOdpManager({ + ...options, + segmentRequestHandler: options.requestHandler, + eventRequestHandler: options.requestHandler, + eventBatchSize: options.eventBatchSize || DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || DEFAULT_FLUSH_INTERVAL, + eventRequestGenerator: eventApiRequestGenerator, + }); +}; diff --git a/lib/vuid/vuid_manager_factory.browser.ts b/lib/vuid/vuid_manager_factory.browser.ts index 8aee22f97..0691fd5e7 100644 --- a/lib/vuid/vuid_manager_factory.browser.ts +++ b/lib/vuid/vuid_manager_factory.browser.ts @@ -26,4 +26,3 @@ export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidM enableVuid: options.enableVuid })); }; - From ca88ea45936c1fd6accbbf4a58b59474b0c91efe Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 14 May 2025 03:21:47 +0600 Subject: [PATCH 158/200] [FSSDK-11504] rename log level presets (#1049) --- lib/common_exports.ts | 8 ++++---- lib/entrypoint.test-d.ts | 8 ++++---- lib/entrypoint.universal.test-d.ts | 8 ++++---- lib/event_processor/batch_event_processor.spec.ts | 2 +- lib/logging/logger_factory.spec.ts | 6 +++--- lib/logging/logger_factory.ts | 8 ++++---- lib/project_config/polling_datafile_manager.spec.ts | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/common_exports.ts b/lib/common_exports.ts index 93ae7db47..801fb7728 100644 --- a/lib/common_exports.ts +++ b/lib/common_exports.ts @@ -19,10 +19,10 @@ export { createStaticProjectConfigManager } from './project_config/config_manage export { LogLevel } from './logging/logger'; export { - DebugLog, - InfoLog, - WarnLog, - ErrorLog, + DEBUG, + INFO, + WARN, + ERROR, } from './logging/logger_factory'; export { createLogger } from './logging/logger_factory'; diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts index b60537ea5..5dcf2e73e 100644 --- a/lib/entrypoint.test-d.ts +++ b/lib/entrypoint.test-d.ts @@ -78,10 +78,10 @@ export type Entrypoint = { // logger related exports LogLevel: typeof LogLevel; - DebugLog: OpaqueLevelPreset, - InfoLog: OpaqueLevelPreset, - WarnLog: OpaqueLevelPreset, - ErrorLog: OpaqueLevelPreset, + DEBUG: OpaqueLevelPreset, + INFO: OpaqueLevelPreset, + WARN: OpaqueLevelPreset, + ERROR: OpaqueLevelPreset, createLogger: (config: LoggerConfig) => OpaqueLogger; // error related exports diff --git a/lib/entrypoint.universal.test-d.ts b/lib/entrypoint.universal.test-d.ts index 0dcc3a671..2fa1891d4 100644 --- a/lib/entrypoint.universal.test-d.ts +++ b/lib/entrypoint.universal.test-d.ts @@ -73,10 +73,10 @@ export type UniversalEntrypoint = { // logger related exports LogLevel: typeof LogLevel; - DebugLog: OpaqueLevelPreset, - InfoLog: OpaqueLevelPreset, - WarnLog: OpaqueLevelPreset, - ErrorLog: OpaqueLevelPreset, + DEBUG: OpaqueLevelPreset, + INFO: OpaqueLevelPreset, + WARN: OpaqueLevelPreset, + ERROR: OpaqueLevelPreset, createLogger: (config: LoggerConfig) => OpaqueLogger; // error related exports diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index a01a60f33..aa25d39e7 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -179,7 +179,7 @@ describe('BatchEventProcessor', async () => { batchSize: 100, }); - expect(processor.process(createImpressionEvent('id-1'))).rejects.toThrow(); + await expect(processor.process(createImpressionEvent('id-1'))).rejects.toThrow(); }); it('should enqueue event without dispatching immediately', async () => { diff --git a/lib/logging/logger_factory.spec.ts b/lib/logging/logger_factory.spec.ts index b39524c6e..6910ab67a 100644 --- a/lib/logging/logger_factory.spec.ts +++ b/lib/logging/logger_factory.spec.ts @@ -25,7 +25,7 @@ vi.mock('./logger', async (importOriginal) => { }); import { OptimizelyLogger, ConsoleLogHandler, LogLevel } from './logger'; -import { createLogger, extractLogger, InfoLog } from './logger_factory'; +import { createLogger, extractLogger, INFO } from './logger_factory'; import { errorResolver, infoResolver } from '../message/message_resolver'; describe('create', () => { @@ -41,7 +41,7 @@ describe('create', () => { const mockLogHandler = { log: vi.fn() }; const logger = extractLogger(createLogger({ - level: InfoLog, + level: INFO, logHandler: mockLogHandler, })); @@ -56,7 +56,7 @@ describe('create', () => { it('should use a ConsoleLogHandler if no logHandler is provided', () => { const logger = extractLogger(createLogger({ - level: InfoLog, + level: INFO, })); expect(logger).toBe(MockedOptimizelyLogger.mock.instances[0]); diff --git a/lib/logging/logger_factory.ts b/lib/logging/logger_factory.ts index 0b4335d01..9830acd48 100644 --- a/lib/logging/logger_factory.ts +++ b/lib/logging/logger_factory.ts @@ -50,19 +50,19 @@ export type OpaqueLevelPreset = { [levelPresetSymbol]: unknown; }; -export const DebugLog: OpaqueLevelPreset = { +export const DEBUG: OpaqueLevelPreset = { [levelPresetSymbol]: debugPreset, }; -export const InfoLog: OpaqueLevelPreset = { +export const INFO: OpaqueLevelPreset = { [levelPresetSymbol]: infoPreset, }; -export const WarnLog: OpaqueLevelPreset = { +export const WARN: OpaqueLevelPreset = { [levelPresetSymbol]: warnPreset, }; -export const ErrorLog: OpaqueLevelPreset = { +export const ERROR: OpaqueLevelPreset = { [levelPresetSymbol]: errorPreset, }; diff --git a/lib/project_config/polling_datafile_manager.spec.ts b/lib/project_config/polling_datafile_manager.spec.ts index a5654fa5d..921ab2a93 100644 --- a/lib/project_config/polling_datafile_manager.spec.ts +++ b/lib/project_config/polling_datafile_manager.spec.ts @@ -495,7 +495,7 @@ describe('PollingDatafileManager', () => { manager.start(); for(let i = 0; i < 2; i++) { const ret = repeater.execute(0); - expect(ret).rejects.toThrow(); + await expect(ret).rejects.toThrow(); } repeater.execute(0); From 36ec43a49f1261536f7c467cf2cfc35f65db09b9 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Wed, 14 May 2025 17:55:11 +0600 Subject: [PATCH 159/200] [FSSDK-11483] content-length header removal from browser (#1047) --- .../event_dispatcher/default_dispatcher.spec.ts | 3 +-- lib/event_processor/event_dispatcher/default_dispatcher.ts | 1 - lib/utils/http_request_handler/request_handler.node.ts | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts index d491bf3a0..45a198788 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts @@ -69,8 +69,7 @@ describe('DefaultEventDispatcher', () => { expect(requestHnadler.makeRequest).toHaveBeenCalledWith( eventObj.url, { - 'content-type': 'application/json', - 'content-length': JSON.stringify(eventObj.params).length.toString(), + 'content-type': 'application/json' }, 'POST', JSON.stringify(eventObj.params) diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.ts b/lib/event_processor/event_dispatcher/default_dispatcher.ts index 30da34823..b786ffda2 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.ts @@ -37,7 +37,6 @@ export class DefaultEventDispatcher implements EventDispatcher { const headers = { 'content-type': 'application/json', - 'content-length': dataString.length.toString(), }; const abortableRequest = this.requestHandler.makeRequest(eventObj.url, headers, 'POST', dataString); diff --git a/lib/utils/http_request_handler/request_handler.node.ts b/lib/utils/http_request_handler/request_handler.node.ts index 7e64a7383..16af94caf 100644 --- a/lib/utils/http_request_handler/request_handler.node.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -59,6 +59,7 @@ export class NodeRequestHandler implements RequestHandler { headers: { ...headers, 'accept-encoding': 'gzip,deflate', + 'content-length': String(data?.length || 0) }, timeout: this.timeout, }); From db81531f3323409c9c1f8f003650e74299444840 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Wed, 14 May 2025 19:52:20 +0600 Subject: [PATCH 160/200] [FSSDK-11502] async decide methods addition (#1050) --- lib/optimizely_user_context/index.ts | 38 ++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index 75259feb8..7b2af6488 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2024, Optimizely + * Copyright 2020-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,8 +39,11 @@ export interface IOptimizelyUserContext { getAttributes(): UserAttributes; setAttribute(key: string, value: unknown): void; decide(key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision; + decideAsync(key: string, options?: OptimizelyDecideOption[]): Promise<OptimizelyDecision>; decideForKeys(keys: string[], options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; + decideForKeysAsync(keys: string[], options?: OptimizelyDecideOption[]): Promise<Record<string, OptimizelyDecision>>; decideAll(options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; + decideAllAsync(options?: OptimizelyDecideOption[]): Promise<Record<string, OptimizelyDecision>>; trackEvent(eventName: string, eventTags?: EventTags): void; setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean; getForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null; @@ -60,7 +63,7 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { constructor({ optimizely, userId, attributes }: OptimizelyUserContextConfig) { this.optimizely = optimizely; this.userId = userId; - this.attributes = { ...attributes } ?? {}; + this.attributes = { ...attributes }; this.forcedDecisionsMap = {}; } @@ -104,6 +107,17 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { return this.optimizely.decide(this.cloneUserContext(), key, options); } + /** + * Returns a promise that resolves in decision result for a given flag key and a user context, which contains all data required to deliver the flag. + * If the SDK finds an error, it will return a decision with null for variationKey. The decision will include an error message in reasons. + * @param {string} key A flag key for which a decision will be made. + * @param {OptimizelyDecideOption} options An array of options for decision-making. + * @return {Promise<OptimizelyDecision>} A Promise that resolves decision result. + */ + decideAsync(key: string, options?: OptimizelyDecideOption[]): Promise<OptimizelyDecision> { + return this.optimizely.decideAsync(this.cloneUserContext(), key, options); + } + /** * Returns an object of decision results for multiple flag keys and a user context. * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. @@ -116,6 +130,17 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { return this.optimizely.decideForKeys(this.cloneUserContext(), keys, options); } + /** + * Returns a promise that resolves in an object of decision results for multiple flag keys and a user context. + * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. + * The SDK will always return key-mapped decisions. When it cannot process requests, it will return an empty map after logging the errors. + * @param {string[]} keys An array of flag keys for which decisions will be made. + * @param {OptimizelyDecideOption[]} options An array of options for decision-making. + * @return {Promise<Record<string, OptimizelyDecision>>} A promise that resolves in an object of decision results mapped by flag keys. + */ + decideForKeysAsync(keys: string[], options?: OptimizelyDecideOption[]): Promise<Record<string, OptimizelyDecision>> { + return this.optimizely.decideForKeysAsync(this.cloneUserContext(), keys, options); + } /** * Returns an object of decision results for all active flag keys. * @param {OptimizelyDecideOption[]} options An array of options for decision-making. @@ -125,6 +150,15 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { return this.optimizely.decideAll(this.cloneUserContext(), options); } + /** + * Returns a promise that resolves in an object of decision results for all active flag keys. + * @param {OptimizelyDecideOption[]} options An array of options for decision-making. + * @return {Promise<Record<string ,OptimizelyDecision>>} A promise that resolves in an object of all decision results mapped by flag keys. + */ + decideAllAsync(options: OptimizelyDecideOption[] = []): Promise<Record<string, OptimizelyDecision>> { + return this.optimizely.decideAllAsync(this.cloneUserContext(), options); + } + /** * Tracks an event. * @param {string} eventName The event name. From e1190c67bc95d70664ae0f67d2b317162742e973 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 15 May 2025 20:52:12 +0600 Subject: [PATCH 161/200] [FSSDK-11505] update cache interface (#1051) --- .../decision_service/cmab/cmab_service.ts | 12 +- lib/event_processor/batch_event_processor.ts | 12 +- .../event_processor_factory.browser.spec.ts | 18 +- .../event_processor_factory.browser.ts | 4 +- .../event_processor_factory.node.spec.ts | 32 ++-- ...ent_processor_factory.react_native.spec.ts | 40 ++-- .../event_processor_factory.react_native.ts | 4 +- .../event_processor_factory.ts | 20 +- .../segment_manager/odp_segment_manager.ts | 10 +- lib/project_config/config_manager_factory.ts | 6 +- lib/project_config/datafile_manager.ts | 6 +- .../polling_datafile_manager.ts | 6 +- lib/tests/mock/mock_cache.ts | 35 +++- .../cache/async_storage_cache.react_native.ts | 6 +- lib/utils/cache/cache.ts | 148 ++------------- lib/utils/cache/in_memory_lru_cache.spec.ts | 100 +++++----- lib/utils/cache/in_memory_lru_cache.ts | 16 +- .../cache/local_storage_cache.browser.ts | 6 +- .../cache/{cache.spec.ts => store.spec.ts} | 120 +++--------- lib/utils/cache/store.ts | 174 ++++++++++++++++++ lib/vuid/vuid_manager.ts | 14 +- lib/vuid/vuid_manager_factory.ts | 6 +- 22 files changed, 400 insertions(+), 395 deletions(-) rename lib/utils/cache/{cache.spec.ts => store.spec.ts} (71%) create mode 100644 lib/utils/cache/store.ts diff --git a/lib/core/decision_service/cmab/cmab_service.ts b/lib/core/decision_service/cmab/cmab_service.ts index b4f958fbf..094e10bbb 100644 --- a/lib/core/decision_service/cmab/cmab_service.ts +++ b/lib/core/decision_service/cmab/cmab_service.ts @@ -18,7 +18,7 @@ import { LoggerFacade } from "../../../logging/logger"; import { IOptimizelyUserContext } from "../../../optimizely_user_context"; import { ProjectConfig } from "../../../project_config/project_config" import { OptimizelyDecideOption, UserAttributes } from "../../../shared_types" -import { Cache } from "../../../utils/cache/cache"; +import { Cache, CacheWithRemove } from "../../../utils/cache/cache"; import { CmabClient } from "./cmab_client"; import { v4 as uuidV4 } from 'uuid'; import murmurhash from "murmurhash"; @@ -53,12 +53,12 @@ export type CmabCacheValue = { export type CmabServiceOptions = { logger?: LoggerFacade; - cmabCache: Cache<CmabCacheValue>; + cmabCache: CacheWithRemove<CmabCacheValue>; cmabClient: CmabClient; } export class DefaultCmabService implements CmabService { - private cmabCache: Cache<CmabCacheValue>; + private cmabCache: CacheWithRemove<CmabCacheValue>; private cmabClient: CmabClient; private logger?: LoggerFacade; @@ -81,7 +81,7 @@ export class DefaultCmabService implements CmabService { } if (options[OptimizelyDecideOption.RESET_CMAB_CACHE]) { - this.cmabCache.clear(); + this.cmabCache.reset(); } const cacheKey = this.getCacheKey(userContext.getUserId(), ruleId); @@ -90,7 +90,7 @@ export class DefaultCmabService implements CmabService { this.cmabCache.remove(cacheKey); } - const cachedValue = await this.cmabCache.get(cacheKey); + const cachedValue = await this.cmabCache.lookup(cacheKey); const attributesJson = JSON.stringify(filteredAttributes, Object.keys(filteredAttributes).sort()); const attributesHash = String(murmurhash.v3(attributesJson)); @@ -104,7 +104,7 @@ export class DefaultCmabService implements CmabService { } const variation = await this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes); - this.cmabCache.set(cacheKey, { + this.cmabCache.save(cacheKey, { attributesHash, variationId: variation.variationId, cmabUuid: variation.cmabUuid, diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index baf7a2d86..5fa7c3f2f 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ import { EventProcessor, ProcessableEvent } from "./event_processor"; -import { Cache } from "../utils/cache/cache"; +import { getBatchedAsync, getBatchedSync, Store } from "../utils/cache/store"; import { EventDispatcher, EventDispatcherResponse, LogEvent } from "./event_dispatcher/event_dispatcher"; import { buildLogEvent } from "./event_builder/log_event"; import { BackoffController, ExponentialBackoff, IntervalRepeater, Repeater } from "../utils/repeater/repeater"; @@ -49,7 +49,7 @@ export type BatchEventProcessorConfig = { dispatchRepeater: Repeater, failedEventRepeater?: Repeater, batchSize: number, - eventStore?: Cache<EventWithId>, + eventStore?: Store<EventWithId>, eventDispatcher: EventDispatcher, closingEventDispatcher?: EventDispatcher, logger?: LoggerFacade, @@ -69,7 +69,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { private closingEventDispatcher?: EventDispatcher; private eventQueue: EventWithId[] = []; private batchSize: number; - private eventStore?: Cache<EventWithId>; + private eventStore?: Store<EventWithId>; private dispatchRepeater: Repeater; private failedEventRepeater?: Repeater; private idGenerator: IdGenerator = new IdGenerator(); @@ -114,7 +114,9 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { (k) => !this.dispatchingEventIds.has(k) && !this.eventQueue.find((e) => e.id === k) ); - const events = await this.eventStore.getBatched(keys); + const events = await (this.eventStore.operation === 'sync' ? + getBatchedSync(this.eventStore, keys) : getBatchedAsync(this.eventStore, keys)); + const failedEvents: EventWithId[] = []; events.forEach((e) => { if(e) { diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts index dcc7ce497..475b36353 100644 --- a/lib/event_processor/event_processor_factory.browser.spec.ts +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,14 +41,14 @@ vi.mock('../utils/cache/local_storage_cache.browser', () => { return { LocalStorageCache: vi.fn() }; }); -vi.mock('../utils/cache/cache', () => { - return { SyncPrefixCache: vi.fn() }; +vi.mock('../utils/cache/store', () => { + return { SyncPrefixStore: vi.fn() }; }); import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; -import { SyncPrefixCache } from '../utils/cache/cache'; +import { SyncPrefixStore } from '../utils/cache/store'; import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.browser'; import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; @@ -85,21 +85,21 @@ describe('createForwardingEventProcessor', () => { describe('createBatchEventProcessor', () => { const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); const MockLocalStorageCache = vi.mocked(LocalStorageCache); - const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); + const MockSyncPrefixStore = vi.mocked(SyncPrefixStore); beforeEach(() => { mockGetOpaqueBatchEventProcessor.mockClear(); MockLocalStorageCache.mockClear(); - MockSyncPrefixCache.mockClear(); + MockSyncPrefixStore.mockClear(); }); - it('uses LocalStorageCache and SyncPrefixCache to create eventStore', () => { + it('uses LocalStorageCache and SyncPrefixStore to create eventStore', () => { const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; - expect(Object.is(eventStore, MockSyncPrefixCache.mock.results[0].value)).toBe(true); + expect(Object.is(eventStore, MockSyncPrefixStore.mock.results[0].value)).toBe(true); - const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixStore.mock.calls[0]; expect(Object.is(cache, MockLocalStorageCache.mock.results[0].value)).toBe(true); expect(prefix).toBe(EVENT_STORE_PREFIX); diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index 39d8e169d..ff53b0298 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -27,7 +27,7 @@ import { import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; -import { SyncPrefixCache } from '../utils/cache/cache'; +import { SyncPrefixStore } from '../utils/cache/store'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; export const DEFAULT_EVENT_BATCH_SIZE = 10; @@ -45,7 +45,7 @@ export const createBatchEventProcessor = ( options: BatchEventProcessorOptions = {} ): OpaqueEventProcessor => { const localStorageCache = new LocalStorageCache<EventWithId>(); - const eventStore = new SyncPrefixCache<EventWithId, EventWithId>( + const eventStore = new SyncPrefixStore<EventWithId, EventWithId>( localStorageCache, EVENT_STORE_PREFIX, identity, identity, diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts index 487230748..43d65ee44 100644 --- a/lib/event_processor/event_processor_factory.node.spec.ts +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,8 +39,8 @@ vi.mock('../utils/cache/async_storage_cache.react_native', () => { return { AsyncStorageCache: vi.fn() }; }); -vi.mock('../utils/cache/cache', () => { - return { SyncPrefixCache: vi.fn(), AsyncPrefixCache: vi.fn() }; +vi.mock('../utils/cache/store', () => { + return { SyncPrefixStore: vi.fn(), AsyncPrefixStore: vi.fn() }; }); import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor_factory.node'; @@ -48,7 +48,7 @@ import { getForwardingEventProcessor } from './forwarding_event_processor'; import nodeDefaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { getOpaqueBatchEventProcessor } from './event_processor_factory'; -import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; +import { AsyncStore, AsyncPrefixStore, SyncStore, SyncPrefixStore } from '../utils/cache/store'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; describe('createForwardingEventProcessor', () => { @@ -80,14 +80,14 @@ describe('createForwardingEventProcessor', () => { describe('createBatchEventProcessor', () => { const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); - const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); - const MockAsyncPrefixCache = vi.mocked(AsyncPrefixCache); + const MockSyncPrefixStore = vi.mocked(SyncPrefixStore); + const MockAsyncPrefixStore = vi.mocked(AsyncPrefixStore); beforeEach(() => { mockGetOpaqueBatchEventProcessor.mockClear(); MockAsyncStorageCache.mockClear(); - MockSyncPrefixCache.mockClear(); - MockAsyncPrefixCache.mockClear(); + MockSyncPrefixStore.mockClear(); + MockAsyncPrefixStore.mockClear(); }); it('uses no default event store if no eventStore is provided', () => { @@ -98,16 +98,16 @@ describe('createBatchEventProcessor', () => { expect(eventStore).toBe(undefined); }); - it('wraps the provided eventStore in a SyncPrefixCache if a SyncCache is provided as eventStore', () => { + it('wraps the provided eventStore in a SyncPrefixStore if a SyncCache is provided as eventStore', () => { const eventStore = { operation: 'sync', - } as SyncCache<string>; + } as SyncStore<string>; const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); - const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixStore.mock.calls[0]; expect(cache).toBe(eventStore); expect(prefix).toBe(EVENT_STORE_PREFIX); @@ -117,16 +117,16 @@ describe('createBatchEventProcessor', () => { expect(transformSet({ value: 1 })).toBe('{"value":1}'); }); - it('wraps the provided eventStore in a AsyncPrefixCache if a AsyncCache is provided as eventStore', () => { + it('wraps the provided eventStore in a AsyncPrefixStore if a AsyncCache is provided as eventStore', () => { const eventStore = { operation: 'async', - } as AsyncCache<string>; + } as AsyncStore<string>; const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); - const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixStore.mock.calls[0]; expect(cache).toBe(eventStore); expect(prefix).toBe(EVENT_STORE_PREFIX); diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 131654a79..733b494d2 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,8 +40,8 @@ vi.mock('../utils/cache/async_storage_cache.react_native', () => { return { AsyncStorageCache: vi.fn() }; }); -vi.mock('../utils/cache/cache', () => { - return { SyncPrefixCache: vi.fn(), AsyncPrefixCache: vi.fn() }; +vi.mock('../utils/cache/store', () => { + return { SyncPrefixStore: vi.fn(), AsyncPrefixStore: vi.fn() }; }); vi.mock('@react-native-community/netinfo', () => { @@ -79,7 +79,7 @@ import { getForwardingEventProcessor } from './forwarding_event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL, getPrefixEventStore } from './event_processor_factory'; import { getOpaqueBatchEventProcessor } from './event_processor_factory'; -import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; +import { AsyncStore, AsyncPrefixStore, SyncStore, SyncPrefixStore } from '../utils/cache/store'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; import { BatchEventProcessor } from './batch_event_processor'; @@ -115,15 +115,15 @@ describe('createForwardingEventProcessor', () => { describe('createBatchEventProcessor', () => { const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); - const MockSyncPrefixCache = vi.mocked(SyncPrefixCache); - const MockAsyncPrefixCache = vi.mocked(AsyncPrefixCache); + const MockSyncPrefixStore = vi.mocked(SyncPrefixStore); + const MockAsyncPrefixStore = vi.mocked(AsyncPrefixStore); beforeEach(() => { isNetInfoAvailable = false; mockGetOpaqueBatchEventProcessor.mockClear(); MockAsyncStorageCache.mockClear(); - MockSyncPrefixCache.mockClear(); - MockAsyncPrefixCache.mockClear(); + MockSyncPrefixStore.mockClear(); + MockAsyncPrefixStore.mockClear(); }); it('returns an instance of ReacNativeNetInfoEventProcessor if netinfo can be required', async () => { @@ -140,14 +140,14 @@ describe('createBatchEventProcessor', () => { expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][1]).toBe(BatchEventProcessor); }); - it('uses AsyncStorageCache and AsyncPrefixCache to create eventStore if no eventStore is provided', () => { + it('uses AsyncStorageCache and AsyncPrefixStore to create eventStore if no eventStore is provided', () => { const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; - expect(Object.is(eventStore, MockAsyncPrefixCache.mock.results[0].value)).toBe(true); + expect(Object.is(eventStore, MockAsyncPrefixStore.mock.results[0].value)).toBe(true); - const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixStore.mock.calls[0]; expect(Object.is(cache, MockAsyncStorageCache.mock.results[0].value)).toBe(true); expect(prefix).toBe(EVENT_STORE_PREFIX); @@ -177,7 +177,7 @@ describe('createBatchEventProcessor', () => { isAsyncStorageAvailable = false; const eventStore = { operation: 'sync', - } as SyncCache<string>; + } as SyncStore<string>; const { AsyncStorageCache } = await vi.importActual< typeof import('../utils/cache/async_storage_cache.react_native') @@ -192,16 +192,16 @@ describe('createBatchEventProcessor', () => { isAsyncStorageAvailable = true; }); - it('wraps the provided eventStore in a SyncPrefixCache if a SyncCache is provided as eventStore', () => { + it('wraps the provided eventStore in a SyncPrefixStore if a SyncCache is provided as eventStore', () => { const eventStore = { operation: 'sync', - } as SyncCache<string>; + } as SyncStore<string>; const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); - const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixStore.mock.calls[0]; expect(cache).toBe(eventStore); expect(prefix).toBe(EVENT_STORE_PREFIX); @@ -211,16 +211,16 @@ describe('createBatchEventProcessor', () => { expect(transformSet({ value: 1 })).toBe('{"value":1}'); }); - it('wraps the provided eventStore in a AsyncPrefixCache if a AsyncCache is provided as eventStore', () => { + it('wraps the provided eventStore in a AsyncPrefixStore if a AsyncCache is provided as eventStore', () => { const eventStore = { operation: 'async', - } as AsyncCache<string>; + } as AsyncStore<string>; const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); - const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixStore.mock.calls[0]; expect(cache).toBe(eventStore); expect(prefix).toBe(EVENT_STORE_PREFIX); diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index 66e4a302b..02c0e2cf7 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -25,7 +25,7 @@ import { wrapEventProcessor, } from './event_processor_factory'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; -import { AsyncPrefixCache } from '../utils/cache/cache'; +import { AsyncPrefixStore } from '../utils/cache/store'; import { BatchEventProcessor, EventWithId } from './batch_event_processor'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; @@ -45,7 +45,7 @@ const identity = <T>(v: T): T => v; const getDefaultEventStore = () => { const asyncStorageCache = new AsyncStorageCache<EventWithId>(); - const eventStore = new AsyncPrefixCache<EventWithId, EventWithId>( + const eventStore = new AsyncPrefixStore<EventWithId, EventWithId>( asyncStorageCache, EVENT_STORE_PREFIX, identity, diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index 7be0a1be4..e931d5b1f 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,23 +20,23 @@ import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater import { EventDispatcher } from "./event_dispatcher/event_dispatcher"; import { EventProcessor } from "./event_processor"; import { BatchEventProcessor, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, EventWithId, RetryConfig } from "./batch_event_processor"; -import { AsyncPrefixCache, Cache, SyncPrefixCache } from "../utils/cache/cache"; +import { AsyncPrefixStore, Store, SyncPrefixStore } from "../utils/cache/store"; export const FAILED_EVENT_RETRY_INTERVAL = 20 * 1000; export const EVENT_STORE_PREFIX = 'optly_event:'; -export const getPrefixEventStore = (cache: Cache<string>): Cache<EventWithId> => { - if (cache.operation === 'async') { - return new AsyncPrefixCache<string, EventWithId>( - cache, +export const getPrefixEventStore = (store: Store<string>): Store<EventWithId> => { + if (store.operation === 'async') { + return new AsyncPrefixStore<string, EventWithId>( + store, EVENT_STORE_PREFIX, JSON.parse, JSON.stringify, ); } else { - return new SyncPrefixCache<string, EventWithId>( - cache, + return new SyncPrefixStore<string, EventWithId>( + store, EVENT_STORE_PREFIX, JSON.parse, JSON.stringify, @@ -55,7 +55,7 @@ export type BatchEventProcessorOptions = { closingEventDispatcher?: EventDispatcher; flushInterval?: number; batchSize?: number; - eventStore?: Cache<string>; + eventStore?: Store<string>; }; export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, 'eventDispatcher' | 'eventStore' > & { @@ -64,7 +64,7 @@ export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, failedEventRetryInterval?: number; defaultFlushInterval: number; defaultBatchSize: number; - eventStore?: Cache<EventWithId>; + eventStore?: Store<EventWithId>; retryOptions?: { maxRetries?: number; minBackoff?: number; diff --git a/lib/odp/segment_manager/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts index 8ba589dd4..4ff125672 100644 --- a/lib/odp/segment_manager/odp_segment_manager.ts +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,11 +94,11 @@ export class DefaultOdpSegmentManager implements OdpSegmentManager { const resetCache = options?.includes(OptimizelySegmentOption.RESET_CACHE); if (resetCache) { - this.segmentsCache.clear(); + this.segmentsCache.reset(); } if (!ignoreCache) { - const cachedSegments = await this.segmentsCache.get(cacheKey); + const cachedSegments = await this.segmentsCache.lookup(cacheKey); if (cachedSegments) { return cachedSegments; } @@ -113,7 +113,7 @@ export class DefaultOdpSegmentManager implements OdpSegmentManager { ); if (segments && !ignoreCache) { - this.segmentsCache.set(cacheKey, segments); + this.segmentsCache.save(cacheKey, segments); } return segments; @@ -125,6 +125,6 @@ export class DefaultOdpSegmentManager implements OdpSegmentManager { updateConfig(config: OdpIntegrationConfig): void { this.odpIntegrationConfig = config; - this.segmentsCache.clear(); + this.segmentsCache.reset(); } } diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 6f01c2589..763c235d0 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,12 @@ import { Transformer } from "../utils/type"; import { DatafileManagerConfig } from "./datafile_manager"; import { ProjectConfigManagerImpl, ProjectConfigManager } from "./project_config_manager"; import { PollingDatafileManager } from "./polling_datafile_manager"; -import { Cache } from "../utils/cache/cache"; import { DEFAULT_UPDATE_INTERVAL } from './constant'; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; import { StartupLog } from "../service"; import { MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; import { LogLevel } from '../logging/logger' +import { Store } from "../utils/cache/store"; const configManagerSymbol: unique symbol = Symbol(); @@ -53,7 +53,7 @@ export type PollingConfigManagerConfig = { updateInterval?: number; urlTemplate?: string; datafileAccessToken?: string; - cache?: Cache<string>; + cache?: Store<string>; }; export type PollingConfigManagerFactoryOptions = PollingConfigManagerConfig & { requestHandler: RequestHandler }; diff --git a/lib/project_config/datafile_manager.ts b/lib/project_config/datafile_manager.ts index c1b58704b..c5765a539 100644 --- a/lib/project_config/datafile_manager.ts +++ b/lib/project_config/datafile_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ import { Service, StartupLog } from '../service'; -import { Cache } from '../utils/cache/cache'; +import { Store } from '../utils/cache/store'; import { RequestHandler } from '../utils/http_request_handler/http'; import { Fn, Consumer } from '../utils/type'; import { Repeater } from '../utils/repeater/repeater'; @@ -31,7 +31,7 @@ export type DatafileManagerConfig = { autoUpdate?: boolean; sdkKey: string; urlTemplate?: string; - cache?: Cache<string>; + cache?: Store<string>; datafileAccessToken?: string; initRetry?: number; repeater: Repeater; diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index fbbbeb0e0..ba8e70139 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import { sprintf } from '../utils/fns'; import { DatafileManager, DatafileManagerConfig } from './datafile_manager'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE } from './constant'; -import { Cache } from '../utils/cache/cache'; +import { Store } from '../utils/cache/store'; import { BaseService, ServiceState } from '../service'; import { RequestHandler, AbortableRequest, Headers, Response } from '../utils/http_request_handler/http'; import { Repeater } from '../utils/repeater/repeater'; @@ -53,7 +53,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag private datafileUrl: string; private currentRequest?: AbortableRequest; private cacheKey: string; - private cache?: Cache<string>; + private cache?: Store<string>; private sdkKey: string; private datafileAccessToken?: string; diff --git a/lib/tests/mock/mock_cache.ts b/lib/tests/mock/mock_cache.ts index 5a542deae..21a89e7a4 100644 --- a/lib/tests/mock/mock_cache.ts +++ b/lib/tests/mock/mock_cache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ import { SyncCache, AsyncCache } from "../../utils/cache/cache"; +import { SyncStore, AsyncStore } from "../../utils/cache/store"; import { Maybe } from "../../utils/type"; type SyncCacheWithAddOn<T> = SyncCache<T> & { @@ -27,7 +28,17 @@ type AsyncCacheWithAddOn<T> = AsyncCache<T> & { getAll(): Promise<Map<string, T>>; }; -export const getMockSyncCache = <T>(): SyncCacheWithAddOn<T> => { +type SyncStoreWithAddOn<T> = SyncStore<T> & { + size(): number; + getAll(): Map<string, T>; +}; + +type AsyncStoreWithAddOn<T> = AsyncStore<T> & { + size(): Promise<number>; + getAll(): Promise<Map<string, T>>; +}; + +export const getMockSyncCache = <T>(): SyncCacheWithAddOn<T> & SyncStoreWithAddOn<T> => { const cache = { operation: 'sync' as const, data: new Map<string, T>(), @@ -37,6 +48,9 @@ export const getMockSyncCache = <T>(): SyncCacheWithAddOn<T> => { clear(): void { this.data.clear(); }, + reset(): void { + this.clear(); + }, getKeys(): string[] { return Array.from(this.data.keys()); }, @@ -52,8 +66,14 @@ export const getMockSyncCache = <T>(): SyncCacheWithAddOn<T> => { get(key: string): T | undefined { return this.data.get(key); }, + lookup(key: string): T | undefined { + return this.get(key); + }, set(key: string, value: T): void { this.data.set(key, value); + }, + save(key: string, value: T): void { + this.data.set(key, value); } } @@ -61,7 +81,7 @@ export const getMockSyncCache = <T>(): SyncCacheWithAddOn<T> => { }; -export const getMockAsyncCache = <T>(): AsyncCacheWithAddOn<T> => { +export const getMockAsyncCache = <T>(): AsyncCacheWithAddOn<T> & AsyncStoreWithAddOn<T> => { const cache = { operation: 'async' as const, data: new Map<string, T>(), @@ -71,6 +91,9 @@ export const getMockAsyncCache = <T>(): AsyncCacheWithAddOn<T> => { async clear(): Promise<void> { this.data.clear(); }, + async reset(): Promise<void> { + this.clear(); + }, async getKeys(): Promise<string[]> { return Array.from(this.data.keys()); }, @@ -86,8 +109,14 @@ export const getMockAsyncCache = <T>(): AsyncCacheWithAddOn<T> => { async get(key: string): Promise<Maybe<T>> { return this.data.get(key); }, + async lookup(key: string): Promise<Maybe<T>> { + return this.get(key); + }, async set(key: string, value: T): Promise<void> { this.data.set(key, value); + }, + async save(key: string, value: T): Promise<void> { + return this.set(key, value); } } diff --git a/lib/utils/cache/async_storage_cache.react_native.ts b/lib/utils/cache/async_storage_cache.react_native.ts index 4656496d2..e5e76024e 100644 --- a/lib/utils/cache/async_storage_cache.react_native.ts +++ b/lib/utils/cache/async_storage_cache.react_native.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,10 @@ */ import { Maybe } from "../type"; -import { AsyncCache } from "./cache"; +import { AsyncStore } from "./store"; import { getDefaultAsyncStorage } from "../import.react_native/@react-native-async-storage/async-storage"; -export class AsyncStorageCache<V> implements AsyncCache<V> { +export class AsyncStorageCache<V> implements AsyncStore<V> { public readonly operation = 'async'; private asyncStorage = getDefaultAsyncStorage(); diff --git a/lib/utils/cache/cache.ts b/lib/utils/cache/cache.ts index 46dcebbda..ada8a5ac6 100644 --- a/lib/utils/cache/cache.ts +++ b/lib/utils/cache/cache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,142 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { OpType, OpValue } from '../../utils/type'; -import { Transformer } from '../../utils/type'; -import { Maybe } from '../../utils/type'; - -export type CacheOp = 'sync' | 'async'; -export type OpValue<Op extends CacheOp, V> = Op extends 'sync' ? V : Promise<V>; - -export interface CacheWithOp<Op extends CacheOp, V> { - operation: Op; - set(key: string, value: V): OpValue<Op, unknown>; - get(key: string): OpValue<Op, Maybe<V>>; - remove(key: string): OpValue<Op, unknown>; - clear(): OpValue<Op, unknown>; - getKeys(): OpValue<Op, string[]>; - getBatched(keys: string[]): OpValue<Op, Maybe<V>[]>; +export interface OpCache<OP extends OpType, V> { + operation: OP; + save(key: string, value: V): OpValue<OP, unknown>; + lookup(key: string): OpValue<OP, V | undefined>; + reset(): OpValue<OP, unknown>; } -export type SyncCache<V> = CacheWithOp<'sync', V>; -export type AsyncCache<V> = CacheWithOp<'async', V>; -export type Cache<V> = SyncCache<V> | AsyncCache<V>; - -export class SyncPrefixCache<U, V> implements SyncCache<V> { - private cache: SyncCache<U>; - private prefix: string; - private transformGet: Transformer<U, V>; - private transformSet: Transformer<V, U>; - - public readonly operation = 'sync'; - - constructor( - cache: SyncCache<U>, - prefix: string, - transformGet: Transformer<U, V>, - transformSet: Transformer<V, U> - ) { - this.cache = cache; - this.prefix = prefix; - this.transformGet = transformGet; - this.transformSet = transformSet; - } - - private addPrefix(key: string): string { - return `${this.prefix}${key}`; - } - - private removePrefix(key: string): string { - return key.substring(this.prefix.length); - } - - set(key: string, value: V): unknown { - return this.cache.set(this.addPrefix(key), this.transformSet(value)); - } - - get(key: string): V | undefined { - const value = this.cache.get(this.addPrefix(key)); - return value ? this.transformGet(value) : undefined; - } - - remove(key: string): unknown { - return this.cache.remove(this.addPrefix(key)); - } - - clear(): void { - this.getInternalKeys().forEach((key) => this.cache.remove(key)); - } - - private getInternalKeys(): string[] { - return this.cache.getKeys().filter((key) => key.startsWith(this.prefix)); - } +export type SyncCache<V> = OpCache<'sync', V>; +export type AsyncCache<V> = OpCache<'async', V>; - getKeys(): string[] { - return this.getInternalKeys().map((key) => this.removePrefix(key)); - } +export type Cache<V> = SyncCache<V> | AsyncCache<V>; - getBatched(keys: string[]): Maybe<V>[] { - return this.cache.getBatched(keys.map((key) => this.addPrefix(key))) - .map((value) => value ? this.transformGet(value) : undefined); - } +export interface OpCacheWithRemove<OP extends OpType, V> extends OpCache<OP, V> { + remove(key: string): OpValue<OP, unknown>; } -export class AsyncPrefixCache<U, V> implements AsyncCache<V> { - private cache: AsyncCache<U>; - private prefix: string; - private transformGet: Transformer<U, V>; - private transformSet: Transformer<V, U>; - - public readonly operation = 'async'; - - constructor( - cache: AsyncCache<U>, - prefix: string, - transformGet: Transformer<U, V>, - transformSet: Transformer<V, U> - ) { - this.cache = cache; - this.prefix = prefix; - this.transformGet = transformGet; - this.transformSet = transformSet; - } - - private addPrefix(key: string): string { - return `${this.prefix}${key}`; - } - - private removePrefix(key: string): string { - return key.substring(this.prefix.length); - } - - set(key: string, value: V): Promise<unknown> { - return this.cache.set(this.addPrefix(key), this.transformSet(value)); - } - - async get(key: string): Promise<V | undefined> { - const value = await this.cache.get(this.addPrefix(key)); - return value ? this.transformGet(value) : undefined; - } - - remove(key: string): Promise<unknown> { - return this.cache.remove(this.addPrefix(key)); - } - - async clear(): Promise<void> { - const keys = await this.getInternalKeys(); - await Promise.all(keys.map((key) => this.cache.remove(key))); - } - - private async getInternalKeys(): Promise<string[]> { - return this.cache.getKeys().then((keys) => keys.filter((key) => key.startsWith(this.prefix))); - } - - async getKeys(): Promise<string[]> { - return this.getInternalKeys().then((keys) => keys.map((key) => this.removePrefix(key))); - } - - async getBatched(keys: string[]): Promise<Maybe<V>[]> { - const values = await this.cache.getBatched(keys.map((key) => this.addPrefix(key))); - return values.map((value) => value ? this.transformGet(value) : undefined); - } -} +export type SyncCacheWithRemove<V> = OpCacheWithRemove<'sync', V>; +export type AsyncCacheWithRemove<V> = OpCacheWithRemove<'async', V>; +export type CacheWithRemove<V> = SyncCacheWithRemove<V> | AsyncCacheWithRemove<V>; diff --git a/lib/utils/cache/in_memory_lru_cache.spec.ts b/lib/utils/cache/in_memory_lru_cache.spec.ts index c6ab08780..81c1e4a96 100644 --- a/lib/utils/cache/in_memory_lru_cache.spec.ts +++ b/lib/utils/cache/in_memory_lru_cache.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,105 +20,87 @@ import { wait } from '../../tests/testUtils'; describe('InMemoryLruCache', () => { it('should save and get values correctly', () => { const cache = new InMemoryLruCache<number>(2); - cache.set('a', 1); - cache.set('b', 2); - expect(cache.get('a')).toBe(1); - expect(cache.get('b')).toBe(2); + cache.save('a', 1); + cache.save('b', 2); + expect(cache.lookup('a')).toBe(1); + expect(cache.lookup('b')).toBe(2); }); it('should return undefined for non-existent keys', () => { const cache = new InMemoryLruCache<number>(2); - expect(cache.get('a')).toBe(undefined); + expect(cache.lookup('a')).toBe(undefined); }); it('should return all keys in cache when getKeys is called', () => { const cache = new InMemoryLruCache<number>(20); - cache.set('a', 1); - cache.set('b', 2); - cache.set('c', 3); - cache.set('d', 4); + cache.save('a', 1); + cache.save('b', 2); + cache.save('c', 3); + cache.save('d', 4); expect(cache.getKeys()).toEqual(expect.arrayContaining(['d', 'c', 'b', 'a'])); }); - it('should evict least recently used keys when full', () => { const cache = new InMemoryLruCache<number>(3); - cache.set('a', 1); - cache.set('b', 2); - cache.set('c', 3); + cache.save('a', 1); + cache.save('b', 2); + cache.save('c', 3); - expect(cache.get('b')).toBe(2); - expect(cache.get('c')).toBe(3); - expect(cache.get('a')).toBe(1); + expect(cache.lookup('b')).toBe(2); + expect(cache.lookup('c')).toBe(3); + expect(cache.lookup('a')).toBe(1); expect(cache.getKeys()).toEqual(expect.arrayContaining(['a', 'c', 'b'])); // key use order is now a c b. next insert should evict b - cache.set('d', 4); - expect(cache.get('b')).toBe(undefined); + cache.save('d', 4); + expect(cache.lookup('b')).toBe(undefined); expect(cache.getKeys()).toEqual(expect.arrayContaining(['d', 'a', 'c'])); // key use order is now d a c. setting c should put it at the front - cache.set('c', 5); + cache.save('c', 5); // key use order is now c d a. next insert should evict a - cache.set('e', 6); - expect(cache.get('a')).toBe(undefined); + cache.save('e', 6); + expect(cache.lookup('a')).toBe(undefined); expect(cache.getKeys()).toEqual(expect.arrayContaining(['e', 'c', 'd'])); // key use order is now e c d. reading d should put it at the front - expect(cache.get('d')).toBe(4); + expect(cache.lookup('d')).toBe(4); // key use order is now d e c. next insert should evict c - cache.set('f', 7); - expect(cache.get('c')).toBe(undefined); + cache.save('f', 7); + expect(cache.lookup('c')).toBe(undefined); expect(cache.getKeys()).toEqual(expect.arrayContaining(['f', 'd', 'e'])); }); it('should not return expired values when get is called', async () => { const cache = new InMemoryLruCache<number>(2, 100); - cache.set('a', 1); - cache.set('b', 2); - expect(cache.get('a')).toBe(1); - expect(cache.get('b')).toBe(2); + cache.save('a', 1); + cache.save('b', 2); + expect(cache.lookup('a')).toBe(1); + expect(cache.lookup('b')).toBe(2); await wait(150); - expect(cache.get('a')).toBe(undefined); - expect(cache.get('b')).toBe(undefined); + expect(cache.lookup('a')).toBe(undefined); + expect(cache.lookup('b')).toBe(undefined); }); it('should remove values correctly', () => { const cache = new InMemoryLruCache<number>(2); - cache.set('a', 1); - cache.set('b', 2); - cache.set('c', 3); + cache.save('a', 1); + cache.save('b', 2); + cache.save('c', 3); cache.remove('a'); - expect(cache.get('a')).toBe(undefined); - expect(cache.get('b')).toBe(2); - expect(cache.get('c')).toBe(3); + expect(cache.lookup('a')).toBe(undefined); + expect(cache.lookup('b')).toBe(2); + expect(cache.lookup('c')).toBe(3); }); it('should clear all values correctly', () => { const cache = new InMemoryLruCache<number>(2); - cache.set('a', 1); - cache.set('b', 2); - cache.clear(); - expect(cache.get('a')).toBe(undefined); - expect(cache.get('b')).toBe(undefined); - }); - - it('should return correct values when getBatched is called', () => { - const cache = new InMemoryLruCache<number>(2); - cache.set('a', 1); - cache.set('b', 2); - expect(cache.getBatched(['a', 'b', 'c'])).toEqual([1, 2, undefined]); - }); - - it('should not return expired values when getBatched is called', async () => { - const cache = new InMemoryLruCache<number>(2, 100); - cache.set('a', 1); - cache.set('b', 2); - expect(cache.getBatched(['a', 'b'])).toEqual([1, 2]); - - await wait(150); - expect(cache.getBatched(['a', 'b'])).toEqual([undefined, undefined]); + cache.save('a', 1); + cache.save('b', 2); + cache.reset(); + expect(cache.lookup('a')).toBe(undefined); + expect(cache.lookup('b')).toBe(undefined); }); }); diff --git a/lib/utils/cache/in_memory_lru_cache.ts b/lib/utils/cache/in_memory_lru_cache.ts index 1b4d3a7bd..6ed92d1fd 100644 --- a/lib/utils/cache/in_memory_lru_cache.ts +++ b/lib/utils/cache/in_memory_lru_cache.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,14 @@ */ import { Maybe } from "../type"; -import { SyncCache } from "./cache"; +import { SyncCacheWithRemove } from "./cache"; type CacheElement<V> = { value: V; expiresAt?: number; }; -export class InMemoryLruCache<V> implements SyncCache<V> { +export class InMemoryLruCache<V> implements SyncCacheWithRemove<V> { public operation = 'sync' as const; private data: Map<string, CacheElement<V>> = new Map(); private maxSize: number; @@ -33,7 +33,7 @@ export class InMemoryLruCache<V> implements SyncCache<V> { this.ttl = ttl; } - get(key: string): Maybe<V> { + lookup(key: string): Maybe<V> { const element = this.data.get(key); if (!element) return undefined; this.data.delete(key); @@ -46,7 +46,7 @@ export class InMemoryLruCache<V> implements SyncCache<V> { return element.value; } - set(key: string, value: V): void { + save(key: string, value: V): void { this.data.delete(key); if (this.data.size === this.maxSize) { @@ -64,15 +64,11 @@ export class InMemoryLruCache<V> implements SyncCache<V> { this.data.delete(key); } - clear(): void { + reset(): void { this.data.clear(); } getKeys(): string[] { return Array.from(this.data.keys()); } - - getBatched(keys: string[]): Maybe<V>[] { - return keys.map((key) => this.get(key)); - } } diff --git a/lib/utils/cache/local_storage_cache.browser.ts b/lib/utils/cache/local_storage_cache.browser.ts index 594b722d2..b16d77571 100644 --- a/lib/utils/cache/local_storage_cache.browser.ts +++ b/lib/utils/cache/local_storage_cache.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ */ import { Maybe } from "../type"; -import { SyncCache } from "./cache"; +import { SyncStore } from "./store"; -export class LocalStorageCache<V> implements SyncCache<V> { +export class LocalStorageCache<V> implements SyncStore<V> { public readonly operation = 'sync'; public set(key: string, value: V): void { diff --git a/lib/utils/cache/cache.spec.ts b/lib/utils/cache/store.spec.ts similarity index 71% rename from lib/utils/cache/cache.spec.ts rename to lib/utils/cache/store.spec.ts index 150fe4884..a99226844 100644 --- a/lib/utils/cache/cache.spec.ts +++ b/lib/utils/cache/store.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,28 +15,28 @@ */ import { describe, it, expect } from 'vitest'; -import { SyncPrefixCache, AsyncPrefixCache } from './cache'; +import { SyncPrefixStore, AsyncPrefixStore } from './store'; import { getMockSyncCache, getMockAsyncCache } from '../../tests/mock/mock_cache'; -describe('SyncPrefixCache', () => { +describe('SyncPrefixStore', () => { describe('set', () => { it('should add prefix to key when setting in the underlying cache', () => { const cache = getMockSyncCache<string>(); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); prefixCache.set('key', 'value'); expect(cache.get('prefix:key')).toEqual('value'); }); it('should transform value when setting in the underlying cache', () => { const cache = getMockSyncCache<string>(); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); prefixCache.set('key', 'value'); expect(cache.get('prefix:key')).toEqual('VALUE'); }); it('should work correctly with empty prefix', () => { const cache = getMockSyncCache<string>(); - const prefixCache = new SyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); prefixCache.set('key', 'value'); expect(cache.get('key')).toEqual('VALUE'); }); @@ -46,13 +46,13 @@ describe('SyncPrefixCache', () => { it('should remove prefix from key when getting from the underlying cache', () => { const cache = getMockSyncCache<string>(); cache.set('prefix:key', 'value'); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); expect(prefixCache.get('key')).toEqual('value'); }); it('should transform value after getting from the underlying cache', () => { const cache = getMockSyncCache<string>(); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); cache.set('prefix:key', 'VALUE'); expect(prefixCache.get('key')).toEqual('value'); }); @@ -60,7 +60,7 @@ describe('SyncPrefixCache', () => { it('should work correctly with empty prefix', () => { const cache = getMockSyncCache<string>(); - const prefixCache = new SyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); cache.set('key', 'VALUE'); expect(prefixCache.get('key')).toEqual('value'); }); @@ -71,7 +71,7 @@ describe('SyncPrefixCache', () => { const cache = getMockSyncCache<string>(); cache.set('prefix:key', 'value'); cache.set('key', 'value'); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); prefixCache.remove('key'); expect(cache.get('prefix:key')).toBeUndefined(); expect(cache.get('key')).toEqual('value'); @@ -80,42 +80,12 @@ describe('SyncPrefixCache', () => { it('should work with empty prefix', () => { const cache = getMockSyncCache<string>(); cache.set('key', 'value'); - const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v, (v) => v); prefixCache.remove('key'); expect(cache.get('key')).toBeUndefined(); }); }); - describe('clear', () => { - it('should remove keys with correct prefix from the underlying cache', () => { - const cache = getMockSyncCache<string>(); - cache.set('key1', 'value1'); - cache.set('key2', 'value2'); - cache.set('prefix:key1', 'value1'); - cache.set('prefix:key2', 'value2'); - - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); - prefixCache.clear(); - - expect(cache.get('key1')).toEqual('value1'); - expect(cache.get('key2')).toEqual('value2'); - expect(cache.get('prefix:key1')).toBeUndefined(); - expect(cache.get('prefix:key2')).toBeUndefined(); - }); - - it('should work with empty prefix', () => { - const cache = getMockSyncCache<string>(); - cache.set('key1', 'value1'); - cache.set('key2', 'value2'); - - const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); - prefixCache.clear(); - - expect(cache.get('key1')).toBeUndefined(); - expect(cache.get('key2')).toBeUndefined(); - }); - }); - describe('getKeys', () => { it('should return keys with correct prefix', () => { const cache = getMockSyncCache<string>(); @@ -124,7 +94,7 @@ describe('SyncPrefixCache', () => { cache.set('prefix:key3', 'value1'); cache.set('prefix:key4', 'value2'); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); const keys = prefixCache.getKeys(); expect(keys).toEqual(expect.arrayContaining(['key3', 'key4'])); @@ -135,7 +105,7 @@ describe('SyncPrefixCache', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); - const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v, (v) => v); const keys = prefixCache.getKeys(); expect(keys).toEqual(expect.arrayContaining(['key1', 'key2'])); @@ -151,7 +121,7 @@ describe('SyncPrefixCache', () => { cache.set('prefix:key1', 'prefix:value1'); cache.set('prefix:key2', 'prefix:value2'); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); const values = prefixCache.getBatched(['key1', 'key2', 'key3']); expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); @@ -165,7 +135,7 @@ describe('SyncPrefixCache', () => { cache.set('prefix:key1', 'PREFIX:VALUE1'); cache.set('prefix:key2', 'PREFIX:VALUE2'); - const prefixCache = new SyncPrefixCache(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); const values = prefixCache.getBatched(['key1', 'key2', 'key3']); expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); @@ -176,7 +146,7 @@ describe('SyncPrefixCache', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); - const prefixCache = new SyncPrefixCache(cache, '', (v) => v, (v) => v); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v, (v) => v); const values = prefixCache.getBatched(['key1', 'key2']); expect(values).toEqual(expect.arrayContaining(['value1', 'value2'])); @@ -184,25 +154,25 @@ describe('SyncPrefixCache', () => { }); }); -describe('AsyncPrefixCache', () => { +describe('AsyncPrefixStore', () => { describe('set', () => { it('should add prefix to key when setting in the underlying cache', async () => { const cache = getMockAsyncCache<string>(); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); await prefixCache.set('key', 'value'); expect(await cache.get('prefix:key')).toEqual('value'); }); it('should transform value when setting in the underlying cache', async () => { const cache = getMockAsyncCache<string>(); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); await prefixCache.set('key', 'value'); expect(await cache.get('prefix:key')).toEqual('VALUE'); }); it('should work correctly with empty prefix', async () => { const cache = getMockAsyncCache<string>(); - const prefixCache = new AsyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); await prefixCache.set('key', 'value'); expect(await cache.get('key')).toEqual('VALUE'); }); @@ -212,13 +182,13 @@ describe('AsyncPrefixCache', () => { it('should remove prefix from key when getting from the underlying cache', async () => { const cache = getMockAsyncCache<string>(); await cache.set('prefix:key', 'value'); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); expect(await prefixCache.get('key')).toEqual('value'); }); it('should transform value after getting from the underlying cache', async () => { const cache = getMockAsyncCache<string>(); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); await cache.set('prefix:key', 'VALUE'); expect(await prefixCache.get('key')).toEqual('value'); }); @@ -226,7 +196,7 @@ describe('AsyncPrefixCache', () => { it('should work correctly with empty prefix', async () => { const cache = getMockAsyncCache<string>(); - const prefixCache = new AsyncPrefixCache(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); await cache.set('key', 'VALUE'); expect(await prefixCache.get('key')).toEqual('value'); }); @@ -237,7 +207,7 @@ describe('AsyncPrefixCache', () => { const cache = getMockAsyncCache<string>(); cache.set('prefix:key', 'value'); cache.set('key', 'value'); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); await prefixCache.remove('key'); expect(await cache.get('prefix:key')).toBeUndefined(); expect(await cache.get('key')).toEqual('value'); @@ -246,42 +216,12 @@ describe('AsyncPrefixCache', () => { it('should work with empty prefix', async () => { const cache = getMockAsyncCache<string>(); await cache.set('key', 'value'); - const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v, (v) => v); await prefixCache.remove('key'); expect(await cache.get('key')).toBeUndefined(); }); }); - describe('clear', () => { - it('should remove keys with correct prefix from the underlying cache', async () => { - const cache = getMockAsyncCache<string>(); - await cache.set('key1', 'value1'); - await cache.set('key2', 'value2'); - await cache.set('prefix:key1', 'value1'); - await cache.set('prefix:key2', 'value2'); - - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); - await prefixCache.clear(); - - expect(await cache.get('key1')).toEqual('value1'); - expect(await cache.get('key2')).toEqual('value2'); - expect(await cache.get('prefix:key1')).toBeUndefined(); - expect(await cache.get('prefix:key2')).toBeUndefined(); - }); - - it('should work with empty prefix', async () => { - const cache = getMockAsyncCache<string>(); - await cache.set('key1', 'value1'); - await cache.set('key2', 'value2'); - - const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); - await prefixCache.clear(); - - expect(await cache.get('key1')).toBeUndefined(); - expect(await cache.get('key2')).toBeUndefined(); - }); - }); - describe('getKeys', () => { it('should return keys with correct prefix', async () => { const cache = getMockAsyncCache<string>(); @@ -290,7 +230,7 @@ describe('AsyncPrefixCache', () => { await cache.set('prefix:key3', 'value1'); await cache.set('prefix:key4', 'value2'); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); const keys = await prefixCache.getKeys(); expect(keys).toEqual(expect.arrayContaining(['key3', 'key4'])); @@ -301,7 +241,7 @@ describe('AsyncPrefixCache', () => { await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); - const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v, (v) => v); const keys = await prefixCache.getKeys(); expect(keys).toEqual(expect.arrayContaining(['key1', 'key2'])); @@ -317,7 +257,7 @@ describe('AsyncPrefixCache', () => { await cache.set('prefix:key1', 'prefix:value1'); await cache.set('prefix:key2', 'prefix:value2'); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); const values = await prefixCache.getBatched(['key1', 'key2', 'key3']); expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); @@ -331,7 +271,7 @@ describe('AsyncPrefixCache', () => { await cache.set('prefix:key1', 'PREFIX:VALUE1'); await cache.set('prefix:key2', 'PREFIX:VALUE2'); - const prefixCache = new AsyncPrefixCache(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); const values = await prefixCache.getBatched(['key1', 'key2', 'key3']); expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); @@ -342,7 +282,7 @@ describe('AsyncPrefixCache', () => { await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); - const prefixCache = new AsyncPrefixCache(cache, '', (v) => v, (v) => v); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v, (v) => v); const values = await prefixCache.getBatched(['key1', 'key2']); expect(values).toEqual(expect.arrayContaining(['value1', 'value2'])); diff --git a/lib/utils/cache/store.ts b/lib/utils/cache/store.ts new file mode 100644 index 000000000..c13852f65 --- /dev/null +++ b/lib/utils/cache/store.ts @@ -0,0 +1,174 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transformer } from '../../utils/type'; +import { Maybe } from '../../utils/type'; +import { OpType, OpValue } from '../../utils/type'; + +export interface OpStore<OP extends OpType, V> { + operation: OP; + set(key: string, value: V): OpValue<OP, unknown>; + get(key: string): OpValue<OP, Maybe<V>>; + remove(key: string): OpValue<OP, unknown>; + getKeys(): OpValue<OP, string[]>; +} + +export type SyncStore<V> = OpStore<'sync', V>; +export type AsyncStore<V> = OpStore<'async', V>; +export type Store<V> = SyncStore<V> | AsyncStore<V>; + +export abstract class SyncStoreWithBatchedGet<V> implements SyncStore<V> { + operation = 'sync' as const; + abstract set(key: string, value: V): unknown; + abstract get(key: string): Maybe<V>; + abstract remove(key: string): unknown; + abstract getKeys(): string[]; + abstract getBatched(keys: string[]): Maybe<V>[]; +} + +export abstract class AsyncStoreWithBatchedGet<V> implements AsyncStore<V> { + operation = 'async' as const; + abstract set(key: string, value: V): Promise<unknown>; + abstract get(key: string): Promise<Maybe<V>>; + abstract remove(key: string): Promise<unknown>; + abstract getKeys(): Promise<string[]>; + abstract getBatched(keys: string[]): Promise<Maybe<V>[]>; +} + +export const getBatchedSync = <V>(store: SyncStore<V>, keys: string[]): Maybe<V>[] => { + if (store instanceof SyncStoreWithBatchedGet) { + return store.getBatched(keys); + } + return keys.map((key) => store.get(key)); +}; + +export const getBatchedAsync = <V>(store: AsyncStore<V>, keys: string[]): Promise<Maybe<V>[]> => { + if (store instanceof AsyncStoreWithBatchedGet) { + return store.getBatched(keys); + } + return Promise.all(keys.map((key) => store.get(key))); +}; + +export class SyncPrefixStore<U, V> extends SyncStoreWithBatchedGet<V> implements SyncStore<V> { + private store: SyncStore<U>; + private prefix: string; + private transformGet: Transformer<U, V>; + private transformSet: Transformer<V, U>; + + public readonly operation = 'sync'; + + constructor( + store: SyncStore<U>, + prefix: string, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U> + ) { + super(); + this.store = store; + this.prefix = prefix; + this.transformGet = transformGet; + this.transformSet = transformSet; + } + + private addPrefix(key: string): string { + return `${this.prefix}${key}`; + } + + private removePrefix(key: string): string { + return key.substring(this.prefix.length); + } + + set(key: string, value: V): unknown { + return this.store.set(this.addPrefix(key), this.transformSet(value)); + } + + get(key: string): V | undefined { + const value = this.store.get(this.addPrefix(key)); + return value ? this.transformGet(value) : undefined; + } + + remove(key: string): unknown { + return this.store.remove(this.addPrefix(key)); + } + + private getInternalKeys(): string[] { + return this.store.getKeys().filter((key) => key.startsWith(this.prefix)); + } + + getKeys(): string[] { + return this.getInternalKeys().map((key) => this.removePrefix(key)); + } + + getBatched(keys: string[]): Maybe<V>[] { + return getBatchedSync(this.store, keys.map((key) => this.addPrefix(key))) + .map((value) => value ? this.transformGet(value) : undefined); + } +} + +export class AsyncPrefixStore<U, V> implements AsyncStore<V> { + private cache: AsyncStore<U>; + private prefix: string; + private transformGet: Transformer<U, V>; + private transformSet: Transformer<V, U>; + + public readonly operation = 'async'; + + constructor( + cache: AsyncStore<U>, + prefix: string, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U> + ) { + this.cache = cache; + this.prefix = prefix; + this.transformGet = transformGet; + this.transformSet = transformSet; + } + + private addPrefix(key: string): string { + return `${this.prefix}${key}`; + } + + private removePrefix(key: string): string { + return key.substring(this.prefix.length); + } + + set(key: string, value: V): Promise<unknown> { + return this.cache.set(this.addPrefix(key), this.transformSet(value)); + } + + async get(key: string): Promise<V | undefined> { + const value = await this.cache.get(this.addPrefix(key)); + return value ? this.transformGet(value) : undefined; + } + + remove(key: string): Promise<unknown> { + return this.cache.remove(this.addPrefix(key)); + } + + private async getInternalKeys(): Promise<string[]> { + return this.cache.getKeys().then((keys) => keys.filter((key) => key.startsWith(this.prefix))); + } + + async getKeys(): Promise<string[]> { + return this.getInternalKeys().then((keys) => keys.map((key) => this.removePrefix(key))); + } + + async getBatched(keys: string[]): Promise<Maybe<V>[]> { + const values = await getBatchedAsync(this.cache, keys.map((key) => this.addPrefix(key))); + return values.map((value) => value ? this.transformGet(value) : undefined); + } +} diff --git a/lib/vuid/vuid_manager.ts b/lib/vuid/vuid_manager.ts index 32ca67103..dd0c0322a 100644 --- a/lib/vuid/vuid_manager.ts +++ b/lib/vuid/vuid_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2024, Optimizely + * Copyright 2022-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ import { LoggerFacade } from '../logging/logger'; -import { Cache } from '../utils/cache/cache'; +import { Store } from '../utils/cache/store'; import { AsyncProducer, Maybe } from '../utils/type'; import { isVuid, makeVuid } from './vuid'; @@ -27,7 +27,7 @@ export interface VuidManager { export class VuidCacheManager { private logger?: LoggerFacade; private vuidCacheKey = 'optimizely-vuid'; - private cache?: Cache<string>; + private cache?: Store<string>; // if this value is not undefined, this means the same value is in the cache. // if this is undefined, it could either mean that there is no value in the cache // or that there is a value in the cache but it has not been loaded yet or failed @@ -35,12 +35,12 @@ export class VuidCacheManager { private vuid?: string; private waitPromise: Promise<unknown> = Promise.resolve(); - constructor(cache?: Cache<string>, logger?: LoggerFacade) { + constructor(cache?: Store<string>, logger?: LoggerFacade) { this.cache = cache; this.logger = logger; } - setCache(cache: Cache<string>): void { + setCache(cache: Store<string>): void { this.cache = cache; this.vuid = undefined; } @@ -92,14 +92,14 @@ export class VuidCacheManager { export type VuidManagerConfig = { enableVuid?: boolean; - vuidCache: Cache<string>; + vuidCache: Store<string>; vuidCacheManager: VuidCacheManager; } export class DefaultVuidManager implements VuidManager { private vuidCacheManager: VuidCacheManager; private vuid?: string; - private vuidCache: Cache<string>; + private vuidCache: Store<string>; private vuidEnabled = false; constructor(config: VuidManagerConfig) { diff --git a/lib/vuid/vuid_manager_factory.ts b/lib/vuid/vuid_manager_factory.ts index 61ac36966..ccc4ce2b2 100644 --- a/lib/vuid/vuid_manager_factory.ts +++ b/lib/vuid/vuid_manager_factory.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Cache } from '../utils/cache/cache'; +import { Store } from '../utils/cache/store'; import { VuidManager } from './vuid_manager'; export type VuidManagerOptions = { - vuidCache?: Cache<string>; + vuidCache?: Store<string>; enableVuid?: boolean; } From 3c63d79b42614ab2f4e8e749f8ff7ba8ebc46687 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 15 May 2025 23:18:37 +0600 Subject: [PATCH 162/200] update OptimizelyError message resolution (#1052) --- lib/error/optimizly_error.ts | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/error/optimizly_error.ts b/lib/error/optimizly_error.ts index 658008bea..76a07511a 100644 --- a/lib/error/optimizly_error.ts +++ b/lib/error/optimizly_error.ts @@ -30,22 +30,11 @@ export class OptimizelyError extends Error { // custom Errors when TS is compiled to es5 Object.setPrototypeOf(this, OptimizelyError.prototype); } - - getMessage(resolver?: MessageResolver): string { - if (this.resolved) { - return this.message; - } - - if (resolver) { - this.setMessage(resolver); - return this.message; - } - - return this.baseMessage; - } setMessage(resolver: MessageResolver): void { - this.message = sprintf(resolver.resolve(this.baseMessage), ...this.params); - this.resolved = true; + if (!this.resolved) { + this.message = sprintf(resolver.resolve(this.baseMessage), ...this.params); + this.resolved = true; + } } } From 4d2a4e12bcbb9e7720a8197124eab8dca3c41b42 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 15 May 2025 23:50:42 +0600 Subject: [PATCH 163/200] [FSSDK-11513] limit number of events in the eventStore (#1053) --- .../batch_event_processor.spec.ts | 159 +++++++++++++++++- lib/event_processor/batch_event_processor.ts | 75 ++++++--- lib/message/log_message.ts | 1 + 3 files changed, 214 insertions(+), 21 deletions(-) diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index aa25d39e7..f89f9b5ef 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -16,17 +16,18 @@ import { expect, describe, it, vi, beforeEach, afterEach, MockInstance } from 'vitest'; import { EventWithId, BatchEventProcessor, LOGGER_NAME } from './batch_event_processor'; -import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { getMockAsyncCache, getMockSyncCache } from '../tests/mock/mock_cache'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ProcessableEvent } from './event_processor'; import { buildLogEvent } from './event_builder/log_event'; -import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { ResolvablePromise, resolvablePromise } from '../utils/promise/resolvablePromise'; import { advanceTimersByTime } from '../tests/testUtils'; import { getMockLogger } from '../tests/mock/mock_logger'; import { getMockRepeater } from '../tests/mock/mock_repeater'; import * as retry from '../utils/executor/backoff_retry_runner'; import { ServiceState, StartupLog } from '../service'; import { LogLevel } from '../logging/logger'; +import { IdGenerator } from '../utils/id_generator'; const getMockDispatcher = () => { return { @@ -366,6 +367,160 @@ describe('BatchEventProcessor', async () => { expect(events).toEqual(eventsInStore); }); + + it('should not store the event in the eventStore but still dispatch if the \ + number of pending events is greater than the limit', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue(resolvablePromise().promise); + + const eventStore = getMockSyncCache<EventWithId>(); + + const idGenerator = new IdGenerator(); + + for (let i = 0; i < 505; i++) { + const event = createImpressionEvent(`id-${i}`); + const cacheId = idGenerator.getId(); + await eventStore.set(cacheId, { id: cacheId, event }); + } + + expect(eventStore.size()).toEqual(505); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 1, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 2; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(505); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(507); + expect(eventDispatcher.dispatchEvent.mock.calls[505][0]).toEqual(buildLogEvent([events[0]])); + expect(eventDispatcher.dispatchEvent.mock.calls[506][0]).toEqual(buildLogEvent([events[1]])); + }); + + it('should store events in the eventStore when the number of events in the store\ + becomes lower than the limit', async () => { + const eventDispatcher = getMockDispatcher(); + + const dispatchResponses: ResolvablePromise<any>[] = []; + + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockImplementation((arg) => { + const dispatchResponse = resolvablePromise(); + dispatchResponses.push(dispatchResponse); + return dispatchResponse.promise; + }); + + const eventStore = getMockSyncCache<EventWithId>(); + + const idGenerator = new IdGenerator(); + + for (let i = 0; i < 502; i++) { + const event = createImpressionEvent(`id-${i}`); + const cacheId = String(i); + await eventStore.set(cacheId, { id: cacheId, event }); + } + + expect(eventStore.size()).toEqual(502); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 1, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + let events: ProcessableEvent[] = []; + for(let i = 0; i < 2; i++) { + const event = createImpressionEvent(`id-${i + 502}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(502); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(504); + + expect(eventDispatcher.dispatchEvent.mock.calls[502][0]).toEqual(buildLogEvent([events[0]])); + expect(eventDispatcher.dispatchEvent.mock.calls[503][0]).toEqual(buildLogEvent([events[1]])); + + // resolve the dispatch for events not saved in the store + dispatchResponses[502].resolve({ statusCode: 200 }); + dispatchResponses[503].resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + expect(eventStore.size()).toEqual(502); + + // resolve the dispatch for 3 events in store, making the store size 499 which is lower than the limit + dispatchResponses[0].resolve({ statusCode: 200 }); + dispatchResponses[1].resolve({ statusCode: 200 }); + dispatchResponses[2].resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + expect(eventStore.size()).toEqual(499); + + // process 2 more events + events = []; + for(let i = 0; i < 2; i++) { + const event = createImpressionEvent(`id-${i + 504}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(500); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(506); + expect(eventDispatcher.dispatchEvent.mock.calls[504][0]).toEqual(buildLogEvent([events[0]])); + expect(eventDispatcher.dispatchEvent.mock.calls[505][0]).toEqual(buildLogEvent([events[1]])); + }); + + it('should still dispatch events even if the store save fails', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const eventStore = getMockAsyncCache<EventWithId>(); + // Simulate failure in saving to store + eventStore.set = vi.fn().mockRejectedValue(new Error('Failed to save')); + + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + await dispatchRepeater.execute(0); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); + }); }); it('should dispatch events when dispatchRepeater is triggered', async () => { diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 5fa7c3f2f..bf0ed3f39 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -18,10 +18,10 @@ import { EventProcessor, ProcessableEvent } from "./event_processor"; import { getBatchedAsync, getBatchedSync, Store } from "../utils/cache/store"; import { EventDispatcher, EventDispatcherResponse, LogEvent } from "./event_dispatcher/event_dispatcher"; import { buildLogEvent } from "./event_builder/log_event"; -import { BackoffController, ExponentialBackoff, IntervalRepeater, Repeater } from "../utils/repeater/repeater"; +import { BackoffController, ExponentialBackoff, Repeater } from "../utils/repeater/repeater"; import { LoggerFacade } from '../logging/logger'; import { BaseService, ServiceState, StartupLog } from "../service"; -import { Consumer, Fn, Producer } from "../utils/type"; +import { Consumer, Fn, Maybe, Producer } from "../utils/type"; import { RunResult, runWithRetry } from "../utils/executor/backoff_retry_runner"; import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; import { EventEmitter } from "../utils/event_emitter/event_emitter"; @@ -31,13 +31,16 @@ import { FAILED_TO_DISPATCH_EVENTS, SERVICE_NOT_RUNNING } from "error_message"; import { OptimizelyError } from "../error/optimizly_error"; import { sprintf } from "../utils/fns"; import { SERVICE_STOPPED_BEFORE_RUNNING } from "../service"; +import { EVENT_STORE_FULL } from "../message/log_message"; export const DEFAULT_MIN_BACKOFF = 1000; export const DEFAULT_MAX_BACKOFF = 32000; +export const MAX_EVENTS_IN_STORE = 500; export type EventWithId = { id: string; event: ProcessableEvent; + notStored?: boolean; }; export type RetryConfig = { @@ -59,7 +62,7 @@ export type BatchEventProcessorConfig = { type EventBatch = { request: LogEvent, - ids: string[], + events: EventWithId[], } export const LOGGER_NAME = 'BatchEventProcessor'; @@ -70,11 +73,13 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { private eventQueue: EventWithId[] = []; private batchSize: number; private eventStore?: Store<EventWithId>; + private eventCountInStore: Maybe<number> = undefined; + private maxEventsInStore: number = MAX_EVENTS_IN_STORE; private dispatchRepeater: Repeater; private failedEventRepeater?: Repeater; private idGenerator: IdGenerator = new IdGenerator(); private runningTask: Map<string, RunResult<EventDispatcherResponse>> = new Map(); - private dispatchingEventIds: Set<string> = new Set(); + private dispatchingEvents: Map<string, EventWithId> = new Map(); private eventEmitter: EventEmitter<{ dispatch: LogEvent }> = new EventEmitter(); private retryConfig?: RetryConfig; @@ -84,11 +89,13 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { this.closingEventDispatcher = config.closingEventDispatcher; this.batchSize = config.batchSize; this.eventStore = config.eventStore; + this.retryConfig = config.retryConfig; this.dispatchRepeater = config.dispatchRepeater; this.dispatchRepeater.setTask(() => this.flush()); + this.maxEventsInStore = Math.max(2 * config.batchSize, MAX_EVENTS_IN_STORE); this.failedEventRepeater = config.failedEventRepeater; this.failedEventRepeater?.setTask(() => this.retryFailedEvents()); if (config.logger) { @@ -111,7 +118,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } const keys = (await this.eventStore.getKeys()).filter( - (k) => !this.dispatchingEventIds.has(k) && !this.eventQueue.find((e) => e.id === k) + (k) => !this.dispatchingEvents.has(k) && !this.eventQueue.find((e) => e.id === k) ); const events = await (this.eventStore.operation === 'sync' ? @@ -138,7 +145,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { (currentBatch.length > 0 && !areEventContextsEqual(currentBatch[0].event, event.event))) { batches.push({ request: buildLogEvent(currentBatch.map((e) => e.event)), - ids: currentBatch.map((e) => e.id), + events: currentBatch, }); currentBatch = []; } @@ -148,7 +155,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { if (currentBatch.length > 0) { batches.push({ request: buildLogEvent(currentBatch.map((e) => e.event)), - ids: currentBatch.map((e) => e.id), + events: currentBatch, }); } @@ -163,15 +170,15 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } const events: ProcessableEvent[] = []; - const ids: string[] = []; + const eventWithIds: EventWithId[] = []; this.eventQueue.forEach((event) => { events.push(event.event); - ids.push(event.id); + eventWithIds.push(event); }); this.eventQueue = []; - return { request: buildLogEvent(events), ids }; + return { request: buildLogEvent(events), events: eventWithIds }; } private async executeDispatch(request: LogEvent, closing = false): Promise<EventDispatcherResponse> { @@ -185,10 +192,10 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } private dispatchBatch(batch: EventBatch, closing: boolean): void { - const { request, ids } = batch; + const { request, events } = batch; - ids.forEach((id) => { - this.dispatchingEventIds.add(id); + events.forEach((event) => { + this.dispatchingEvents.set(event.id, event); }); const runResult: RunResult<EventDispatcherResponse> = this.retryConfig @@ -205,9 +212,11 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { this.runningTask.set(taskId, runResult); runResult.result.then((res) => { - ids.forEach((id) => { - this.dispatchingEventIds.delete(id); - this.eventStore?.remove(id); + events.forEach((event) => { + this.eventStore?.remove(event.id); + if (!event.notStored && this.eventCountInStore) { + this.eventCountInStore--; + } }); return Promise.resolve(); }).catch((err) => { @@ -216,7 +225,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { this.logger?.error(err); }).finally(() => { this.runningTask.delete(taskId); - ids.forEach((id) => this.dispatchingEventIds.delete(id)); + events.forEach((event) => this.dispatchingEvents.delete(event.id)); }); } @@ -235,12 +244,12 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { return Promise.reject(new OptimizelyError(SERVICE_NOT_RUNNING, 'BatchEventProcessor')); } - const eventWithId = { + const eventWithId: EventWithId = { id: this.idGenerator.getId(), event: event, }; - await this.eventStore?.set(eventWithId.id, eventWithId); + await this.storeEvent(eventWithId); if (this.eventQueue.length > 0 && !areEventContextsEqual(this.eventQueue[0].event, event)) { this.flush(); @@ -253,7 +262,35 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } else if (!this.dispatchRepeater.isRunning()) { this.dispatchRepeater.start(); } + } + + private async findEventCountInStore(): Promise<void> { + if (this.eventStore && this.eventCountInStore === undefined) { + try { + const keys = await this.eventStore.getKeys(); + this.eventCountInStore = keys.length; + } catch (e) { + this.logger?.error(e); + } + } + } + private async storeEvent(eventWithId: EventWithId): Promise<void> { + await this.findEventCountInStore(); + if (this.eventCountInStore !== undefined && this.eventCountInStore >= this.maxEventsInStore) { + this.logger?.info(EVENT_STORE_FULL, eventWithId.event.uuid); + eventWithId.notStored = true; + return; + } + + await Promise.resolve(this.eventStore?.set(eventWithId.id, eventWithId)).then(() => { + if (this.eventCountInStore !== undefined) { + this.eventCountInStore++; + } + }).catch((e) => { + eventWithId.notStored = true; + this.logger?.error(e); + }); } start(): void { diff --git a/lib/message/log_message.ts b/lib/message/log_message.ts index b4dc35650..c27f5076f 100644 --- a/lib/message/log_message.ts +++ b/lib/message/log_message.ts @@ -60,5 +60,6 @@ export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = 'No experiment %s mapped to user %s in the forced variation map.'; export const INVALID_EXPERIMENT_KEY_INFO = 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; +export const EVENT_STORE_FULL = 'Event store is full. Not saving event with id %d.'; export const messages: string[] = []; From 2b97b449031bcaa329bf1352aa0018ffdb3ef75e Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 16 May 2025 00:58:35 +0600 Subject: [PATCH 164/200] [FSSDK-11509] Remove optional netinfo require (#1054) --- ...batch_event_processor.react_native.spec.ts | 2 +- .../batch_event_processor.react_native.ts | 10 ++--- ...ent_processor_factory.react_native.spec.ts | 27 +------------ .../event_processor_factory.react_native.ts | 31 +++++++-------- .../@react-native-community/netinfo.ts | 38 ------------------- package-lock.json | 21 ++++++---- package.json | 6 +-- 7 files changed, 37 insertions(+), 98 deletions(-) delete mode 100644 lib/utils/import.react_native/@react-native-community/netinfo.ts diff --git a/lib/event_processor/batch_event_processor.react_native.spec.ts b/lib/event_processor/batch_event_processor.react_native.spec.ts index a30717d12..5e17ca966 100644 --- a/lib/event_processor/batch_event_processor.react_native.spec.ts +++ b/lib/event_processor/batch_event_processor.react_native.spec.ts @@ -39,7 +39,7 @@ const mockNetInfo = vi.hoisted(() => { return netInfo; }); -vi.mock('../utils/import.react_native/@react-native-community/netinfo', () => { +vi.mock('@react-native-community/netinfo', () => { return { addEventListener: mockNetInfo.addEventListener.bind(mockNetInfo), }; diff --git a/lib/event_processor/batch_event_processor.react_native.ts b/lib/event_processor/batch_event_processor.react_native.ts index ac5110de4..28741380a 100644 --- a/lib/event_processor/batch_event_processor.react_native.ts +++ b/lib/event_processor/batch_event_processor.react_native.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { NetInfoState, addEventListener } from '../utils/import.react_native/@react-native-community/netinfo'; +import { NetInfoState, addEventListener } from '@react-native-community/netinfo'; import { BatchEventProcessor, BatchEventProcessorConfig } from './batch_event_processor'; import { Fn } from '../utils/type'; @@ -41,15 +41,11 @@ export class ReactNativeNetInfoEventProcessor extends BatchEventProcessor { start(): void { super.start(); - if (addEventListener) { - this.unsubscribeNetInfo = addEventListener(this.connectionListener.bind(this)); - } + this.unsubscribeNetInfo = addEventListener(this.connectionListener.bind(this)); } stop(): void { - if (this.unsubscribeNetInfo) { - this.unsubscribeNetInfo(); - } + this.unsubscribeNetInfo?.(); super.stop(); } } diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 733b494d2..6065e16de 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -47,8 +47,6 @@ vi.mock('../utils/cache/store', () => { vi.mock('@react-native-community/netinfo', () => { return { NetInfoState: {}, addEventListener: vi.fn() }; }); - -let isNetInfoAvailable = false; let isAsyncStorageAvailable = true; await vi.hoisted(async () => { @@ -61,15 +59,10 @@ async function mockRequireNetInfo() { M._load_original = M._load; M._load = (uri: string, parent: string) => { - if (uri === '@react-native-community/netinfo') { - if (isNetInfoAvailable) return {}; - throw new Error("Module not found: @react-native-community/netinfo"); - } if (uri === '@react-native-async-storage/async-storage') { if (isAsyncStorageAvailable) return {}; throw new Error("Module not found: @react-native-async-storage/async-storage"); } - return M._load_original(uri, parent); }; } @@ -77,12 +70,10 @@ async function mockRequireNetInfo() { import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; -import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL, getPrefixEventStore } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { getOpaqueBatchEventProcessor } from './event_processor_factory'; import { AsyncStore, AsyncPrefixStore, SyncStore, SyncPrefixStore } from '../utils/cache/store'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; -import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; -import { BatchEventProcessor } from './batch_event_processor'; import { MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE } from '../utils/import.react_native/@react-native-async-storage/async-storage'; describe('createForwardingEventProcessor', () => { @@ -90,7 +81,6 @@ describe('createForwardingEventProcessor', () => { beforeEach(() => { mockGetForwardingEventProcessor.mockClear(); - isNetInfoAvailable = false; }); it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { @@ -119,27 +109,12 @@ describe('createBatchEventProcessor', () => { const MockAsyncPrefixStore = vi.mocked(AsyncPrefixStore); beforeEach(() => { - isNetInfoAvailable = false; mockGetOpaqueBatchEventProcessor.mockClear(); MockAsyncStorageCache.mockClear(); MockSyncPrefixStore.mockClear(); MockAsyncPrefixStore.mockClear(); }); - it('returns an instance of ReacNativeNetInfoEventProcessor if netinfo can be required', async () => { - isNetInfoAvailable = true; - const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][1]).toBe(ReactNativeNetInfoEventProcessor); - }); - - it('returns an instance of BatchEventProcessor if netinfo cannot be required', async () => { - isNetInfoAvailable = false; - const processor = createBatchEventProcessor({}); - expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][1]).toBe(BatchEventProcessor); - }); - it('uses AsyncStorageCache and AsyncPrefixStore to create eventStore if no eventStore is provided', () => { const processor = createBatchEventProcessor({}); diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index 02c0e2cf7..99019eff0 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -15,7 +15,6 @@ */ import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; -import { EventProcessor } from './event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { BatchEventProcessorOptions, @@ -26,10 +25,9 @@ import { } from './event_processor_factory'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { AsyncPrefixStore } from '../utils/cache/store'; -import { BatchEventProcessor, EventWithId } from './batch_event_processor'; +import { EventWithId } from './batch_event_processor'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; -import { isAvailable as isNetInfoAvailable } from '../utils/import.react_native/@react-native-community/netinfo'; export const DEFAULT_EVENT_BATCH_SIZE = 10; export const DEFAULT_EVENT_FLUSH_INTERVAL = 1_000; @@ -60,17 +58,20 @@ export const createBatchEventProcessor = ( ): OpaqueEventProcessor => { const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : getDefaultEventStore(); - return getOpaqueBatchEventProcessor({ - eventDispatcher: options.eventDispatcher || defaultEventDispatcher, - closingEventDispatcher: options.closingEventDispatcher, - flushInterval: options.flushInterval, - batchSize: options.batchSize, - defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, - defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, - retryOptions: { - maxRetries: 5, + return getOpaqueBatchEventProcessor( + { + eventDispatcher: options.eventDispatcher || defaultEventDispatcher, + closingEventDispatcher: options.closingEventDispatcher, + flushInterval: options.flushInterval, + batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + eventStore, }, - failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, - eventStore, - }, isNetInfoAvailable() ? ReactNativeNetInfoEventProcessor : BatchEventProcessor); + ReactNativeNetInfoEventProcessor + ); }; diff --git a/lib/utils/import.react_native/@react-native-community/netinfo.ts b/lib/utils/import.react_native/@react-native-community/netinfo.ts deleted file mode 100644 index 434a0a1b3..000000000 --- a/lib/utils/import.react_native/@react-native-community/netinfo.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { NetInfoSubscription, NetInfoChangeHandler } from '@react-native-community/netinfo'; -import { Maybe } from '../../type'; - -export { NetInfoState } from '@react-native-community/netinfo'; -export type NetInfoAddEventListerType = (listener: NetInfoChangeHandler) => NetInfoSubscription; - -let addEventListener: Maybe<NetInfoAddEventListerType> = undefined; - -const requireNetInfo = () => { - try { - return require('@react-native-community/netinfo'); - } catch (e) { - return undefined; - } -} - -export const isAvailable = (): boolean => requireNetInfo() !== undefined; - -const netinfo = requireNetInfo(); -addEventListener = netinfo?.addEventListener; - -export { addEventListener }; diff --git a/package-lock.json b/package-lock.json index 331bb0974..919143fa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", + "@react-native-async-storage/async-storage": "^2", "@react-native-community/netinfo": "^11.3.2", "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", @@ -68,10 +68,11 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "^11.3.2", + "@react-native-async-storage/async-storage": ">=1.2.0 <3.0.0", + "@react-native-community/netinfo": ">=10.0.0 <12.0.0", "fast-text-encoding": "^1.0.6", - "react-native-get-random-values": "^1.11.0" + "react-native-get-random-values": "^1.11.0", + "ua-parser-js": "^1.0.38" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { @@ -85,6 +86,9 @@ }, "react-native-get-random-values": { "optional": true + }, + "ua-parser-js": { + "optional": true } } }, @@ -3979,15 +3983,16 @@ } }, "node_modules/@react-native-async-storage/async-storage": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz", - "integrity": "sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", + "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", "dev": true, + "license": "MIT", "dependencies": { "merge-options": "^3.0.4" }, "peerDependencies": { - "react-native": "^0.0.0-0 || >=0.60 <1.0" + "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, "node_modules/@react-native-community/cli": { diff --git a/package.json b/package.json index 6aaac97d4..d67a034bd 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", + "@react-native-async-storage/async-storage": "^2", "@react-native-community/netinfo": "^11.3.2", "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", @@ -147,8 +147,8 @@ "webpack": "^5.74.0" }, "peerDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "^11.3.2", + "@react-native-async-storage/async-storage": ">=1.0.0 <3.0.0", + "@react-native-community/netinfo": ">=5.0.0 <12.0.0", "fast-text-encoding": "^1.0.6", "react-native-get-random-values": "^1.11.0", "ua-parser-js": "^1.0.38" From 1cd028dac468d916774ca34151342fa63b80fb63 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 16 May 2025 04:35:16 +0600 Subject: [PATCH 165/200] [FSSDK-11503] update build target to ES6 (#1055) --- tsconfig.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index c69f440b6..e70a7ce62 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { - "target": "es5", - "module": "esnext", + "target": "ES6", + "module": "ESNext", "lib": [ - "es2015", - "dom" + "ES6", + "DOM", ], "declaration": true, "strict": true, From b70e79ea99b6f221c4cad2656e715fff3a81ba67 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 16 May 2025 19:27:12 +0600 Subject: [PATCH 166/200] [FSSDK-11510] refactor unsupported factories to not throw (#1056) --- lib/entrypoint.test-d.ts | 3 ++- lib/index.browser.ts | 2 +- lib/index.node.ts | 4 ++-- lib/index.react_native.ts | 4 ++-- lib/vuid/vuid_manager_factory.browser.spec.ts | 2 +- lib/vuid/vuid_manager_factory.node.spec.ts | 8 ++++---- lib/vuid/vuid_manager_factory.node.ts | 7 ++----- lib/vuid/vuid_manager_factory.ts | 7 ++++--- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts index 5dcf2e73e..3dd2f3c06 100644 --- a/lib/entrypoint.test-d.ts +++ b/lib/entrypoint.test-d.ts @@ -55,6 +55,7 @@ import { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from './notification_ import { LogLevel } from './logging/logger'; import { OptimizelyDecideOption } from './shared_types'; +import { Maybe } from './utils/type'; export type Entrypoint = { // client factory @@ -66,7 +67,7 @@ export type Entrypoint = { // event processor related exports eventDispatcher: EventDispatcher; - getSendBeaconEventDispatcher: () => EventDispatcher; + getSendBeaconEventDispatcher: () => Maybe<EventDispatcher>; createForwardingEventProcessor: (eventDispatcher?: EventDispatcher) => OpaqueEventProcessor; createBatchEventProcessor: (options?: BatchEventProcessorOptions) => OpaqueEventProcessor; diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 98c7a11d2..96249c6a9 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -45,7 +45,7 @@ export const createInstance = function(config: Config): Client | null { return client; }; -export const getSendBeaconEventDispatcher = (): EventDispatcher => { +export const getSendBeaconEventDispatcher = (): EventDispatcher | undefined => { return sendBeaconEventDispatcher; }; diff --git a/lib/index.node.ts b/lib/index.node.ts index 348c8c3d9..b911d0d6a 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -35,8 +35,8 @@ export const createInstance = function(config: Config): Client | null { return getOptimizelyInstance(nodeConfig); }; -export const getSendBeaconEventDispatcher = function(): EventDispatcher { - throw new Error('Send beacon event dispatcher is not supported in NodeJS'); +export const getSendBeaconEventDispatcher = function(): EventDispatcher | undefined { + return undefined; }; export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.node'; diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index fbdf9c8a0..df386fa66 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -38,8 +38,8 @@ export const createInstance = function(config: Config): Client | null { return getOptimizelyInstance(rnConfig); }; -export const getSendBeaconEventDispatcher = function(): EventDispatcher { - throw new Error('Send beacon event dispatcher is not supported in React Native'); +export const getSendBeaconEventDispatcher = function(): EventDispatcher | undefined { + return undefined; }; export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.browser'; diff --git a/lib/vuid/vuid_manager_factory.browser.spec.ts b/lib/vuid/vuid_manager_factory.browser.spec.ts index 805064c4b..59c8602db 100644 --- a/lib/vuid/vuid_manager_factory.browser.spec.ts +++ b/lib/vuid/vuid_manager_factory.browser.spec.ts @@ -35,7 +35,7 @@ import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; import { extractVuidManager } from './vuid_manager_factory'; -describe('extractVuidManager(createVuidManager', () => { +describe('createVuidManager', () => { const MockVuidCacheManager = vi.mocked(VuidCacheManager); const MockLocalStorageCache = vi.mocked(LocalStorageCache); const MockDefaultVuidManager = vi.mocked(DefaultVuidManager); diff --git a/lib/vuid/vuid_manager_factory.node.spec.ts b/lib/vuid/vuid_manager_factory.node.spec.ts index 0d8a6af5b..8f6b21e74 100644 --- a/lib/vuid/vuid_manager_factory.node.spec.ts +++ b/lib/vuid/vuid_manager_factory.node.spec.ts @@ -17,11 +17,11 @@ import { vi, describe, expect, it } from 'vitest'; import { createVuidManager } from './vuid_manager_factory.node'; -import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from './vuid_manager_factory.node'; +import { extractVuidManager } from './vuid_manager_factory'; describe('createVuidManager', () => { - it('should throw an error', () => { - expect(() => createVuidManager({ enableVuid: true })) - .toThrowError(VUID_IS_NOT_SUPPORTED_IN_NODEJS); + it('should return a undefined vuid manager wrapped as OpaqueVuidManager', () => { + expect(extractVuidManager(createVuidManager({ enableVuid: true }))) + .toBeUndefined(); }); }); diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts index 54dd2dbaa..439e70ec1 100644 --- a/lib/vuid/vuid_manager_factory.node.ts +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -13,11 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { VuidManager } from './vuid_manager'; -import { OpaqueVuidManager, VuidManagerOptions } from './vuid_manager_factory'; - -export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; +import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_manager_factory'; export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidManager => { - throw new Error(VUID_IS_NOT_SUPPORTED_IN_NODEJS); + return wrapVuidManager(undefined); }; diff --git a/lib/vuid/vuid_manager_factory.ts b/lib/vuid/vuid_manager_factory.ts index ccc4ce2b2..94c777e26 100644 --- a/lib/vuid/vuid_manager_factory.ts +++ b/lib/vuid/vuid_manager_factory.ts @@ -15,6 +15,7 @@ */ import { Store } from '../utils/cache/store'; +import { Maybe } from '../utils/type'; import { VuidManager } from './vuid_manager'; export type VuidManagerOptions = { @@ -28,11 +29,11 @@ export type OpaqueVuidManager = { [vuidManagerSymbol]: unknown; }; -export const extractVuidManager = (opaqueVuidManager: OpaqueVuidManager): VuidManager => { - return opaqueVuidManager[vuidManagerSymbol] as VuidManager; +export const extractVuidManager = (opaqueVuidManager: OpaqueVuidManager): Maybe<VuidManager> => { + return opaqueVuidManager[vuidManagerSymbol] as Maybe<VuidManager>; }; -export const wrapVuidManager = (vuidManager: VuidManager): OpaqueVuidManager => { +export const wrapVuidManager = (vuidManager: Maybe<VuidManager>): OpaqueVuidManager => { return { [vuidManagerSymbol]: vuidManager } From a59bd3b3cb5dffabd1447a6cce71b79c202910b7 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 19 May 2025 13:17:46 +0600 Subject: [PATCH 167/200] [FSSDK-11510] update retryConfig for batch event processor factories (#1057) --- .../batch_event_processor.spec.ts | 49 +------------------ lib/event_processor/batch_event_processor.ts | 2 +- .../event_processor_factory.node.spec.ts | 4 +- .../event_processor_factory.node.ts | 3 +- .../event_processor_factory.spec.ts | 45 +++++------------ .../event_processor_factory.ts | 2 +- 6 files changed, 18 insertions(+), 87 deletions(-) diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index f89f9b5ef..a95dd262f 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -639,53 +639,6 @@ describe('BatchEventProcessor', async () => { } }); - it('should retry indefinitely using the provided backoffController if maxRetry is undefined', async () => { - const eventDispatcher = getMockDispatcher(); - const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; - mockDispatch.mockRejectedValue(new Error()); - const dispatchRepeater = getMockRepeater(); - - const backoffController = { - backoff: vi.fn().mockReturnValue(1000), - reset: vi.fn(), - }; - - const processor = new BatchEventProcessor({ - eventDispatcher, - dispatchRepeater, - retryConfig: { - backoffProvider: () => backoffController, - }, - batchSize: 100, - }); - - processor.start(); - await processor.onRunning(); - - const events: ProcessableEvent[] = []; - for(let i = 0; i < 10; i++) { - const event = createImpressionEvent(`id-${i}`); - events.push(event); - await processor.process(event); - } - - expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); - await dispatchRepeater.execute(0); - - for(let i = 0; i < 200; i++) { - await exhaustMicrotasks(); - await advanceTimersByTime(1000); - } - - expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(201); - expect(backoffController.backoff).toHaveBeenCalledTimes(200); - - const request = buildLogEvent(events); - for(let i = 0; i < 201; i++) { - expect(eventDispatcher.dispatchEvent.mock.calls[i][0]).toEqual(request); - } - }); - it('should remove the events from the eventStore after dispatch is successfull', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index bf0ed3f39..48ce32927 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -44,7 +44,7 @@ export type EventWithId = { }; export type RetryConfig = { - maxRetries?: number; + maxRetries: number; backoffProvider: Producer<BackoffController>; } diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts index 43d65ee44..512865381 100644 --- a/lib/event_processor/event_processor_factory.node.spec.ts +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -187,10 +187,10 @@ describe('createBatchEventProcessor', () => { expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); }); - it('uses maxRetries value of 10', () => { + it('uses maxRetries value of 5', () => { const processor = createBatchEventProcessor({ }); expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); - expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(10); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); }); it('uses no failed event retry if an eventStore is not provided', () => { diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index 6ef10be9f..cdcb533a1 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -47,7 +47,8 @@ export const createBatchEventProcessor = ( defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, retryOptions: { - maxRetries: 10, + maxRetries: 5, + }, failedEventRetryInterval: eventStore ? FAILED_EVENT_RETRY_INTERVAL : undefined, eventStore, diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts index c0ea8cb5a..fc57a5097 100644 --- a/lib/event_processor/event_processor_factory.spec.ts +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,24 +83,8 @@ describe('getBatchEventProcessor', () => { expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig).toBe(undefined); }); - it('uses retry when retryOptions is provided', () => { - const options = { - eventDispatcher: getMockEventDispatcher(), - retryOptions: {}, - defaultFlushInterval: 1000, - defaultBatchSize: 10, - }; - - const processor = getBatchEventProcessor(options); - - expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); - const usedRetryConfig = MockBatchEventProcessor.mock.calls[0][0].retryConfig; - expect(usedRetryConfig).not.toBe(undefined); - expect(usedRetryConfig?.backoffProvider).not.toBe(undefined); - }); - it('uses the correct maxRetries value when retryOptions is provided', () => { - const options1 = { + const options = { eventDispatcher: getMockEventDispatcher(), defaultFlushInterval: 1000, defaultBatchSize: 10, @@ -109,21 +93,9 @@ describe('getBatchEventProcessor', () => { }, }; - const processor1 = getBatchEventProcessor(options1); - expect(Object.is(processor1, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig?.maxRetries).toBe(10); - - const options2 = { - eventDispatcher: getMockEventDispatcher(), - defaultFlushInterval: 1000, - defaultBatchSize: 10, - retryOptions: {}, - }; - - const processor2 = getBatchEventProcessor(options2); - expect(Object.is(processor2, MockBatchEventProcessor.mock.instances[1])).toBe(true); - expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig).not.toBe(undefined); - expect(MockBatchEventProcessor.mock.calls[1][0].retryConfig?.maxRetries).toBe(undefined); }); it('uses exponential backoff with default parameters when retryOptions is provided without backoff values', () => { @@ -131,12 +103,14 @@ describe('getBatchEventProcessor', () => { eventDispatcher: getMockEventDispatcher(), defaultFlushInterval: 1000, defaultBatchSize: 10, - retryOptions: {}, + retryOptions: { maxRetries: 2 }, }; const processor = getBatchEventProcessor(options); expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig?.maxRetries).toBe(2); + const backoffProvider = MockBatchEventProcessor.mock.calls[0][0].retryConfig?.backoffProvider; expect(backoffProvider).not.toBe(undefined); const backoff = backoffProvider?.(); @@ -149,11 +123,14 @@ describe('getBatchEventProcessor', () => { eventDispatcher: getMockEventDispatcher(), defaultFlushInterval: 1000, defaultBatchSize: 10, - retryOptions: { minBackoff: 1000, maxBackoff: 2000 }, + retryOptions: { maxRetries: 2, minBackoff: 1000, maxBackoff: 2000 }, }; const processor = getBatchEventProcessor(options); expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig?.maxRetries).toBe(2); + const backoffProvider = MockBatchEventProcessor.mock.calls[0][0].retryConfig?.backoffProvider; expect(backoffProvider).not.toBe(undefined); diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index e931d5b1f..3fff90c9f 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -66,7 +66,7 @@ export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, defaultBatchSize: number; eventStore?: Store<EventWithId>; retryOptions?: { - maxRetries?: number; + maxRetries: number; minBackoff?: number; maxBackoff?: number; }; From 6f983eb2a0683d25f1f541bfd52c834e962596fe Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 19 May 2025 16:39:23 +0600 Subject: [PATCH 168/200] [FSSDK-11515] changelog update (#1058) --- CHANGELOG.md | 13 +++++++++++++ lib/export_types.ts | 1 + 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0fd73fdf..0903ae80f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.3.5] - Jan 29, 2025 + +### Bug Fixes + +- Rollout experiment key exclusion from activate method([#949](https://github.com/optimizely/javascript-sdk/pull/949)) +- Using optimizely.readyPromise instead of optimizely.onReady to avoid setTimeout call in edge environments. ([#995](https://github.com/optimizely/javascript-sdk/pull/995)) + +## [4.10.1] - November 18, 2024 + +### Changed +- update uuid module improt and usage ([#961](https://github.com/optimizely/javascript-sdk/pull/961)) + + ## [5.3.4] - Jun 28, 2024 ### Changed diff --git a/lib/export_types.ts b/lib/export_types.ts index 42e6778b9..53d5c876c 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -65,6 +65,7 @@ export type { ErrorHandler } from './error/error_handler'; export type { OpaqueErrorNotifier } from './error/error_notifier_factory'; export type { Cache } from './utils/cache/cache'; +export type { Store } from './utils/cache/store' export type { NotificationType, From c477d60373d4a1f0b61d5364b303d9e10e8ffeb0 Mon Sep 17 00:00:00 2001 From: Farhan Anjum <Farhan.Anjum@optimizely.com> Date: Mon, 19 May 2025 18:37:26 +0600 Subject: [PATCH 169/200] [FSSDK-11446] update: experiment_id and variation_id added to notification listener payloads (#1030) * -added experiment id and variation id to notification listener payload -fixed unit tests to expect experiment id and variation id in notification listener payload * Type changed of notification listener payload --- lib/notification_center/type.ts | 2 ++ lib/optimizely/index.tests.js | 18 ++++++++++++++++++ lib/optimizely/index.ts | 4 ++++ lib/optimizely_user_context/index.tests.js | 6 ++++++ 4 files changed, 30 insertions(+) diff --git a/lib/notification_center/type.ts b/lib/notification_center/type.ts index 75cfb082f..b433c0121 100644 --- a/lib/notification_center/type.ts +++ b/lib/notification_center/type.ts @@ -94,6 +94,8 @@ export type FlagDecisionInfo = { variables: VariablesMap, reasons: string[], decisionEventDispatched: boolean, + experimentId: string | null, + variationId: string | null, }; export type DecisionInfo = { diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 77ce8e0f1..a7107c479 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -4602,6 +4602,8 @@ describe('lib/optimizely', function() { variables: { i_42: 42 }, decisionEventDispatched: true, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, }, ]; @@ -4652,6 +4654,8 @@ describe('lib/optimizely', function() { variables: { i_42: 42 }, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, }, ]; @@ -4704,6 +4708,8 @@ describe('lib/optimizely', function() { variables: {}, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, }, ]; @@ -4754,6 +4760,8 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: true, reasons: [], + experimentId: '18322080788', + variationId: '18257766532', }, }, ]; @@ -4807,6 +4815,8 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: false, reasons: [], + experimentId: '18322080788', + variationId: '18257766532', }, }, ]; @@ -4857,6 +4867,8 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: true, reasons: [], + experimentId: null, + variationId: null, }, }, ]; @@ -4907,6 +4919,8 @@ describe('lib/optimizely', function() { variables: {}, decisionEventDispatched: true, reasons: [], + experimentId: "10420810910", + variationId: "10418551353", }, }, ]; @@ -4955,6 +4969,8 @@ describe('lib/optimizely', function() { variables: {}, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, }, ]; @@ -5024,6 +5040,8 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, }, ]; diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 883391e4a..0d6c937f8 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1469,7 +1469,9 @@ export default class Optimizely extends BaseService implements Client { const feature = configObj.featureKeyMap[key] const decisionSource = decisionObj.decisionSource; const experimentKey = decisionObj.experiment?.key ?? null; + const experimentId = decisionObj.experiment?.id ?? null; const variationKey = decisionObj.variation?.key ?? null; + const variationId = decisionObj.variation?.id ?? null; const flagEnabled: boolean = decision.getFeatureEnabledFromVariation(decisionObj); const variablesMap: { [key: string]: unknown } = {}; let decisionEventDispatched = false; @@ -1516,6 +1518,8 @@ export default class Optimizely extends BaseService implements Client { variables: variablesMap, reasons: reportedReasons, decisionEventDispatched: decisionEventDispatched, + experimentId: experimentId, + variationId: variationId, }; this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 1ca29ef1a..56457a67c 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -565,6 +565,8 @@ describe('lib/optimizely_user_context', function() { userId ), ], + experimentId: null, + variationId: '3324490562' }, }, ]; @@ -654,6 +656,8 @@ describe('lib/optimizely_user_context', function() { userId ), ], + experimentId: '10390977673', + variationId: '10416523121', }, }, ]; @@ -734,6 +738,8 @@ describe('lib/optimizely_user_context', function() { }, decisionEventDispatched: true, reasons: [], + experimentId: '3332020515', + variationId: '3324490633', }, }, ]; From 0391b47723825e46b64365213ec83180f3078432 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 19 May 2025 20:20:06 +0600 Subject: [PATCH 170/200] [FSSDK-11330] update supported versions of platforms (#1059) --- README.md | 10 +- package-lock.json | 9906 ++++++++++++++++++--------------------------- package.json | 8 +- 3 files changed, 3860 insertions(+), 6064 deletions(-) diff --git a/README.md b/README.md index 86d82035c..9e16f6cd7 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat ### Prerequisites -Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets modern ES5-compliant JavaScript environment. We officially support: -- Node.js >= 16.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. +Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets modern ES6-compliant JavaScript environments. We officially support: +- Node.js >= 18.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. - Modern Web Browsers, such as Microsoft Edge 84+, Firefox 91+, Safari 13+, and Chrome 102+, Opera 76+ In addition, other environments are likely compatible but are not formally supported including: @@ -40,12 +40,6 @@ In addition, other environments are likely compatible but are not formally suppo - [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. - Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 -### Requirements - -* JavaScript (Browser): Modern web browser that is ES5-compliant. - -* JavaScript (Node): Node 16.0.0+ - ### Install the SDK diff --git a/package-lock.json b/package-lock.json index 919143fa8..a1f3df701 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "decompress-response": "^7.0.0", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", - "uuid": "^9.0.1" + "uuid": "^10.0.0" }, "devDependencies": { "@react-native-async-storage/async-storage": "^2", @@ -24,7 +24,7 @@ "@types/nise": "^1.4.0", "@types/node": "^18.7.18", "@types/ua-parser-js": "^0.7.36", - "@types/uuid": "^9.0.7", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", "@vitest/coverage-istanbul": "^2.0.5", @@ -54,9 +54,7 @@ "rollup-plugin-terser": "^5.3.0", "rollup-plugin-typescript2": "^0.27.1", "sinon": "^2.3.1", - "ts-jest": "^29.1.2", "ts-loader": "^9.3.1", - "ts-mockito": "^2.6.1", "ts-node": "^8.10.2", "tsconfig-paths": "^4.2.0", "tslib": "^2.4.0", @@ -65,11 +63,11 @@ "webpack": "^5.74.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "@react-native-async-storage/async-storage": ">=1.2.0 <3.0.0", - "@react-native-community/netinfo": ">=10.0.0 <12.0.0", + "@react-native-async-storage/async-storage": ">=1.0.0 <3.0.0", + "@react-native-community/netinfo": ">=5.0.0 <12.0.0", "fast-text-encoding": "^1.0.6", "react-native-get-random-values": "^1.11.0", "ua-parser-js": "^1.0.38" @@ -812,19 +810,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", @@ -2501,13 +2486,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "peer": true - }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -2517,32 +2495,6 @@ "node": ">=0.1.90" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -3252,915 +3204,640 @@ "node": ">=8" } }, - "node_modules/@jest/console": { + "node_modules/@jest/create-cache-key-function": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", "dev": true, "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "@jest/types": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/core": { + "node_modules/@jest/environment": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "peer": true, "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } } }, - "node_modules/@jest/core/node_modules/@jest/transform": { + "node_modules/@jest/fake-timers": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "peer": true, "dependencies": { - "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/core/node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "peer": true, "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" } }, - "node_modules/@jest/core/node_modules/babel-plugin-jest-hoist": { + "node_modules/@jest/types": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "peer": true, "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/core/node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, - "peer": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=6.0.0" } }, - "node_modules/@jest/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true - }, - "node_modules/@jest/core/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, - "optional": true, - "peer": true, "engines": { - "node": ">=0.3.1" + "node": ">=6.0.0" } }, - "node_modules/@jest/core/node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">=6.0.0" } }, - "node_modules/@jest/core/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "node_modules/@jest/core/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "license": "MIT" }, - "node_modules/@jest/core/node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.3" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 8" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "peer": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 8" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "peer": true, "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 8" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "peer": true, - "dependencies": { - "jest-get-type": "^29.6.3" - }, + "optional": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=14" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", + "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "merge-options": "^3.0.4" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/@react-native-community/cli": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-14.0.0.tgz", + "integrity": "sha512-KwMKJB5jsDxqOhT8CGJ55BADDAYxlYDHv5R/ASQlEcdBEZxT0zZmnL0iiq2VqzETUy+Y/Nop+XDFgqyoQm0C2w==", "dev": true, "peer": true, "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@react-native-community/cli-clean": "14.0.0", + "@react-native-community/cli-config": "14.0.0", + "@react-native-community/cli-debugger-ui": "14.0.0", + "@react-native-community/cli-doctor": "14.0.0", + "@react-native-community/cli-server-api": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", + "@react-native-community/cli-types": "14.0.0", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "rnc-cli": "build/bin.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "node_modules/@react-native-community/cli-clean": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-14.0.0.tgz", + "integrity": "sha512-kvHthZTNur/wLLx8WL5Oh+r04zzzFAX16r8xuaLhu9qGTE6Th1JevbsIuiQb5IJqD8G/uZDKgIZ2a0/lONcbJg==", "dev": true, "peer": true, "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2" } }, - "node_modules/@jest/reporters/node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/@react-native-community/cli-config": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-14.0.0.tgz", + "integrity": "sha512-2Nr8KR+dgn1z+HLxT8piguQ1SoEzgKJnOPQKE1uakxWaRFcQ4LOXgzpIAscYwDW6jmQxdNqqbg2cRUoOS7IMtQ==", "dev": true, "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "cosmiconfig": "^9.0.0", + "deepmerge": "^4.3.0", + "fast-glob": "^3.3.2", + "joi": "^17.2.1" } }, - "node_modules/@jest/reporters/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true - }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0.tgz", + "integrity": "sha512-JpfzILfU7eKE9+7AMCAwNJv70H4tJGVv3ZGFqSVoK1YHg5QkVEGsHtoNW8AsqZRS6Fj4os+Fmh+r+z1L36sPmg==", "dev": true, "peer": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" + "serve-static": "^1.13.1" } }, - "node_modules/@jest/reporters/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/@react-native-community/cli-doctor": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-14.0.0.tgz", + "integrity": "sha512-in6jylHjaPUaDzV+JtUblh8m9JYIHGjHOf6Xn57hrmE5Zwzwuueoe9rSMHF1P0mtDgRKrWPzAJVejElddfptWA==", "dev": true, "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "@react-native-community/cli-config": "14.0.0", + "@react-native-community/cli-platform-android": "14.0.0", + "@react-native-community/cli-platform-apple": "14.0.0", + "@react-native-community/cli-platform-ios": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.13.0", + "execa": "^5.0.0", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" } }, - "node_modules/@jest/reporters/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, "peer": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, + "peer": true, "dependencies": { - "@sinclair/typebox": "^0.27.8" + "ansi-regex": "^4.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "node_modules/@react-native-community/cli-platform-android": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-14.0.0.tgz", + "integrity": "sha512-nt7yVz3pGKQXnVa5MAk7zR+1n41kNKD3Hi2OgybH5tVShMBo7JQoL2ZVVH6/y/9wAwI/s7hXJgzf1OIP3sMq+Q==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.2.4", + "logkitty": "^0.7.1" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "node_modules/@react-native-community/cli-platform-apple": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-14.0.0.tgz", + "integrity": "sha512-WniJL8vR4MeIsjqio2hiWWuUYUJEL3/9TDL5aXNwG68hH3tYgK3742+X9C+vRzdjTmf5IKc/a6PwLsdplFeiwQ==", "dev": true, "peer": true, "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.2.4", + "ora": "^5.4.1" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "node_modules/@react-native-community/cli-platform-ios": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-14.0.0.tgz", + "integrity": "sha512-8kxGv7mZ5nGMtueQDq+ndu08f0ikf3Zsqm3Ix8FY5KCXpSgP14uZloO2GlOImq/zFESij+oMhCkZJGggpWpfAw==", "dev": true, "peer": true, "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@react-native-community/cli-platform-apple": "14.0.0" } }, - "node_modules/@jest/test-sequencer/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/@react-native-community/cli-server-api": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0.tgz", + "integrity": "sha512-A0FIsj0QCcDl1rswaVlChICoNbfN+mkrKB5e1ab5tOYeZMMyCHqvU+eFvAvXjHUlIvVI+LbqCkf4IEdQ6H/2AQ==", "dev": true, "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "@react-native-community/cli-debugger-ui": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^6.2.3" } }, - "node_modules/@jest/test-sequencer/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, + "peer": true, "dependencies": { - "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^17.0.8", + "@types/yargs": "^15.0.0", "chalk": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 10.14.2" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", "dev": true, + "peer": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + "@types/yargs-parser": "*" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, "engines": { - "node": ">=6.0.0" + "node": ">= 10" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "engines": { - "node": ">=6.0.0" - } + "peer": true }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "dev": true, + "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "async-limiter": "~1.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "node_modules/@react-native-community/cli-tools": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0.tgz", + "integrity": "sha512-L7GX5hyYYv0ZWbAyIQKzhHuShnwDqlKYB0tqn57wa5riGCaxYuRPTK+u4qy+WRCye7+i8M4Xj6oQtSd4z0T9cA==", "dev": true, - "license": "MIT" + "peer": true, + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@react-native-community/cli-types": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-14.0.0.tgz", + "integrity": "sha512-CMUevd1pOWqvmvutkUiyQT2lNmMHUzSW7NKc1xvHgg39NjbS58Eh2pMzIUP85IwbYNeocfYc3PH19vA/8LnQtg==", "dev": true, + "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "joi": "^17.2.1" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@react-native-community/netinfo": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz", + "integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==", "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "react-native": ">=0.59" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@react-native/assets-registry": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.2.tgz", + "integrity": "sha512-P1dLHjpUeC0AIkDHRYcx0qLMr+p92IPWL3pmczzo6T76Qa9XzruQOYy0jittxyBK91Csn6HHQ/eit8TeXW8MVw==", "dev": true, + "peer": true, "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.2.tgz", + "integrity": "sha512-BIKVh2ZJPkzluUGgCNgpoh6NTHgX8j04FCS0Z/rTmRJ66hir/EUBl8frMFKrOy/6i4VvZEltOWB5eWfHe1AYgw==", "dev": true, + "peer": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@react-native/codegen": "0.75.2" }, "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@react-native/babel-preset": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.2.tgz", + "integrity": "sha512-mprpsas+WdCEMjQZnbDiAC4KKRmmLbMB+o/v4mDqKlH4Mcm7RdtP5t80MZGOVCHlceNp1uEIpXywx69DNwgbgg==", "dev": true, - "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.5", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.5", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-regenerator": "^7.20.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.75.2", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, "engines": { - "node": ">=14" + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@react-native-async-storage/async-storage": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", - "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", + "node_modules/@react-native/codegen": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.2.tgz", + "integrity": "sha512-OkWdbtO2jTkfOXfj3ibIL27rM6LoaEuApOByU2G8X+HS6v9U87uJVJlMIRWBDmnxODzazuHwNVA2/wAmSbucaw==", "dev": true, - "license": "MIT", + "peer": true, "dependencies": { - "merge-options": "^3.0.4" + "@babel/parser": "^7.20.0", + "glob": "^7.1.1", + "hermes-parser": "0.22.0", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "react-native": "^0.0.0-0 || >=0.65 <1.0" + "@babel/preset-env": "^7.1.6" } }, - "node_modules/@react-native-community/cli": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-14.0.0.tgz", - "integrity": "sha512-KwMKJB5jsDxqOhT8CGJ55BADDAYxlYDHv5R/ASQlEcdBEZxT0zZmnL0iiq2VqzETUy+Y/Nop+XDFgqyoQm0C2w==", + "node_modules/@react-native/community-cli-plugin": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.2.tgz", + "integrity": "sha512-/tz0bzVja4FU0aAimzzQ7iYR43peaD6pzksArdrrGhlm8OvFYAQPOYSNeIQVMSarwnkNeg1naFKaeYf1o3++yA==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-clean": "14.0.0", - "@react-native-community/cli-config": "14.0.0", - "@react-native-community/cli-debugger-ui": "14.0.0", - "@react-native-community/cli-doctor": "14.0.0", - "@react-native-community/cli-server-api": "14.0.0", - "@react-native-community/cli-tools": "14.0.0", - "@react-native-community/cli-types": "14.0.0", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "bin": { - "rnc-cli": "build/bin.js" + "@react-native-community/cli-server-api": "14.0.0-alpha.11", + "@react-native-community/cli-tools": "14.0.0-alpha.11", + "@react-native/dev-middleware": "0.75.2", + "@react-native/metro-babel-transformer": "0.75.2", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.3", + "metro-config": "^0.80.3", + "metro-core": "^0.80.3", + "node-fetch": "^2.2.0", + "querystring": "^0.2.1", + "readline": "^1.3.0" }, "engines": { "node": ">=18" } }, - "node_modules/@react-native-community/cli-clean": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-14.0.0.tgz", - "integrity": "sha512-kvHthZTNur/wLLx8WL5Oh+r04zzzFAX16r8xuaLhu9qGTE6Th1JevbsIuiQb5IJqD8G/uZDKgIZ2a0/lONcbJg==", - "dev": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "14.0.0", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2" - } - }, - "node_modules/@react-native-community/cli-config": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-14.0.0.tgz", - "integrity": "sha512-2Nr8KR+dgn1z+HLxT8piguQ1SoEzgKJnOPQKE1uakxWaRFcQ4LOXgzpIAscYwDW6jmQxdNqqbg2cRUoOS7IMtQ==", - "dev": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "14.0.0", - "chalk": "^4.1.2", - "cosmiconfig": "^9.0.0", - "deepmerge": "^4.3.0", - "fast-glob": "^3.3.2", - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0.tgz", - "integrity": "sha512-JpfzILfU7eKE9+7AMCAwNJv70H4tJGVv3ZGFqSVoK1YHg5QkVEGsHtoNW8AsqZRS6Fj4os+Fmh+r+z1L36sPmg==", - "dev": true, - "peer": true, - "dependencies": { - "serve-static": "^1.13.1" - } - }, - "node_modules/@react-native-community/cli-doctor": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-14.0.0.tgz", - "integrity": "sha512-in6jylHjaPUaDzV+JtUblh8m9JYIHGjHOf6Xn57hrmE5Zwzwuueoe9rSMHF1P0mtDgRKrWPzAJVejElddfptWA==", - "dev": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-config": "14.0.0", - "@react-native-community/cli-platform-android": "14.0.0", - "@react-native-community/cli-platform-apple": "14.0.0", - "@react-native-community/cli-platform-ios": "14.0.0", - "@react-native-community/cli-tools": "14.0.0", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.13.0", - "execa": "^5.0.0", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/@react-native/community-cli-plugin/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, "peer": true, "dependencies": { - "ansi-regex": "^4.1.0" + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-14.0.0.tgz", - "integrity": "sha512-nt7yVz3pGKQXnVa5MAk7zR+1n41kNKD3Hi2OgybH5tVShMBo7JQoL2ZVVH6/y/9wAwI/s7hXJgzf1OIP3sMq+Q==", - "dev": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "14.0.0", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.2.4", - "logkitty": "^0.7.1" - } - }, - "node_modules/@react-native-community/cli-platform-apple": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-14.0.0.tgz", - "integrity": "sha512-WniJL8vR4MeIsjqio2hiWWuUYUJEL3/9TDL5aXNwG68hH3tYgK3742+X9C+vRzdjTmf5IKc/a6PwLsdplFeiwQ==", - "dev": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "14.0.0", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.2.4", - "ora": "^5.4.1" + "node": ">= 10.14.2" } }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-14.0.0.tgz", - "integrity": "sha512-8kxGv7mZ5nGMtueQDq+ndu08f0ikf3Zsqm3Ix8FY5KCXpSgP14uZloO2GlOImq/zFESij+oMhCkZJGggpWpfAw==", + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-debugger-ui": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0-alpha.11.tgz", + "integrity": "sha512-0wCNQxhCniyjyMXgR1qXliY180y/2QbvoiYpp2MleGQADr5M1b8lgI4GoyADh5kE+kX3VL0ssjgyxpmbpCD86A==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-platform-apple": "14.0.0" + "serve-static": "^1.13.1" } }, - "node_modules/@react-native-community/cli-server-api": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0.tgz", - "integrity": "sha512-A0FIsj0QCcDl1rswaVlChICoNbfN+mkrKB5e1ab5tOYeZMMyCHqvU+eFvAvXjHUlIvVI+LbqCkf4IEdQ6H/2AQ==", + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-server-api": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0-alpha.11.tgz", + "integrity": "sha512-I7YeYI7S5wSxnQAqeG8LNqhT99FojiGIk87DU0vTp6U8hIMLcA90fUuBAyJY38AuQZ12ZJpGa8ObkhIhWzGkvg==", "dev": true, "peer": true, "dependencies": { - "@react-native-community/cli-debugger-ui": "14.0.0", - "@react-native-community/cli-tools": "14.0.0", + "@react-native-community/cli-debugger-ui": "14.0.0-alpha.11", + "@react-native-community/cli-tools": "14.0.0-alpha.11", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", @@ -4170,24 +3847,26 @@ "ws": "^6.2.3" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-tools": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0-alpha.11.tgz", + "integrity": "sha512-HQCfVnX9aqRdKdLxmQy4fUAUo+YhNGlBV7ZjOayPbuEGWJ4RN+vSy0Cawk7epo7hXd6vKzc7P7y3HlU6Kxs7+w==", "dev": true, "peer": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { + "node_modules/@react-native/community-cli-plugin/node_modules/@types/yargs": { "version": "15.0.19", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", @@ -4197,7 +3876,7 @@ "@types/yargs-parser": "*" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { + "node_modules/@react-native/community-cli-plugin/node_modules/pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", @@ -4213,14 +3892,14 @@ "node": ">= 10" } }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { + "node_modules/@react-native/community-cli-plugin/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, "peer": true }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "node_modules/@react-native/community-cli-plugin/node_modules/ws": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", @@ -4230,119 +3909,115 @@ "async-limiter": "~1.0.0" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0.tgz", - "integrity": "sha512-L7GX5hyYYv0ZWbAyIQKzhHuShnwDqlKYB0tqn57wa5riGCaxYuRPTK+u4qy+WRCye7+i8M4Xj6oQtSd4z0T9cA==", + "node_modules/@react-native/debugger-frontend": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.2.tgz", + "integrity": "sha512-qIC6mrlG8RQOPaYLZQiJwqnPchAVGnHWcVDeQxPMPLkM/D5+PC8tuKWYOwgLcEau3RZlgz7QQNk31Qj2/OJG6Q==", "dev": true, "peer": true, - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" + "engines": { + "node": ">=18" } }, - "node_modules/@react-native-community/cli-types": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-14.0.0.tgz", - "integrity": "sha512-CMUevd1pOWqvmvutkUiyQT2lNmMHUzSW7NKc1xvHgg39NjbS58Eh2pMzIUP85IwbYNeocfYc3PH19vA/8LnQtg==", + "node_modules/@react-native/dev-middleware": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.2.tgz", + "integrity": "sha512-fTC5m2uVjYp1XPaIJBFgscnQjPdGVsl96z/RfLgXDq0HBffyqbg29ttx6yTCx7lIa9Gdvf6nKQom+e+Oa4izSw==", "dev": true, "peer": true, "dependencies": { - "joi": "^17.2.1" + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.75.2", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.13.1", + "ws": "^6.2.2" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@react-native-community/netinfo": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz", - "integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==", + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "peerDependencies": { - "react-native": ">=0.59" + "peer": true, + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/@react-native/assets-registry": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.2.tgz", - "integrity": "sha512-P1dLHjpUeC0AIkDHRYcx0qLMr+p92IPWL3pmczzo6T76Qa9XzruQOYy0jittxyBK91Csn6HHQ/eit8TeXW8MVw==", + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.2.tgz", + "integrity": "sha512-AELeAOCZi3B2vE6SeN+mjpZjjqzqa76yfFBB3L3f3NWiu4dm/YClTGOj+5IVRRgbt8LDuRImhDoaj7ukheXr4Q==", "dev": true, "peer": true, "engines": { "node": ">=18" } }, - "node_modules/@react-native/babel-plugin-codegen": { + "node_modules/@react-native/js-polyfills": { "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.2.tgz", - "integrity": "sha512-BIKVh2ZJPkzluUGgCNgpoh6NTHgX8j04FCS0Z/rTmRJ66hir/EUBl8frMFKrOy/6i4VvZEltOWB5eWfHe1AYgw==", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.2.tgz", + "integrity": "sha512-AtLd3mbiE+FXK2Ru3l2NFOXDhUvzdUsCP4qspUw0haVaO/9xzV97RVD2zz0lur2f/LmZqQ2+KXyYzr7048b5iw==", "dev": true, "peer": true, - "dependencies": { - "@react-native/codegen": "0.75.2" - }, "engines": { "node": ">=18" } }, - "node_modules/@react-native/babel-preset": { + "node_modules/@react-native/metro-babel-transformer": { "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.2.tgz", - "integrity": "sha512-mprpsas+WdCEMjQZnbDiAC4KKRmmLbMB+o/v4mDqKlH4Mcm7RdtP5t80MZGOVCHlceNp1uEIpXywx69DNwgbgg==", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.2.tgz", + "integrity": "sha512-EygglCCuOub2sZ00CSIiEekCXoGL2XbOC6ssOB47M55QKvhdPG/0WBQXvmOmiN42uZgJK99Lj749v4rB0PlPIQ==", "dev": true, "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-generator-functions": "^7.24.3", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", - "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.5", - "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.5", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-regenerator": "^7.20.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.75.2", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" + "@react-native/babel-preset": "0.75.2", + "hermes-parser": "0.22.0", + "nullthrows": "^1.1.1" }, "engines": { "node": ">=18" @@ -4351,3029 +4026,1815 @@ "@babel/core": "*" } }, - "node_modules/@react-native/codegen": { + "node_modules/@react-native/normalize-colors": { "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.2.tgz", - "integrity": "sha512-OkWdbtO2jTkfOXfj3ibIL27rM6LoaEuApOByU2G8X+HS6v9U87uJVJlMIRWBDmnxODzazuHwNVA2/wAmSbucaw==", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.2.tgz", + "integrity": "sha512-nPwWJFtsqNFS/qSG9yDOiSJ64mjG7RCP4X/HXFfyWzCM1jq49h/DYBdr+c3e7AvTKGIdy0gGT3vgaRUHZFVdUQ==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.2.tgz", + "integrity": "sha512-pD5SVCjxc8k+JdoyQ+IlulBTEqJc3S4KUKsmv5zqbNCyETB0ZUvd4Su7bp+lLF6ALxx6KKmbGk8E3LaWEjUFFQ==", "dev": true, "peer": true, "dependencies": { - "@babel/parser": "^7.20.0", - "glob": "^7.1.1", - "hermes-parser": "0.22.0", "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "yargs": "^17.6.2" + "nullthrows": "^1.1.1" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@babel/preset-env": "^7.1.6" + "@types/react": "^18.2.6", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.2.tgz", - "integrity": "sha512-/tz0bzVja4FU0aAimzzQ7iYR43peaD6pzksArdrrGhlm8OvFYAQPOYSNeIQVMSarwnkNeg1naFKaeYf1o3++yA==", + "node_modules/@rollup/plugin-commonjs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", + "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", "dev": true, - "peer": true, "dependencies": { - "@react-native-community/cli-server-api": "14.0.0-alpha.11", - "@react-native-community/cli-tools": "14.0.0-alpha.11", - "@react-native/dev-middleware": "0.75.2", - "@react-native/metro-babel-transformer": "0.75.2", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "querystring": "^0.2.1", - "readline": "^1.3.0" + "@rollup/pluginutils": "^3.0.8", + "commondir": "^1.0.1", + "estree-walker": "^1.0.1", + "glob": "^7.1.2", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0" }, "engines": { - "node": ">=18" + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "node_modules/@rollup/plugin-node-resolve": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", + "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", "dev": true, - "peer": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "@rollup/pluginutils": "^3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.14.2" }, "engines": { - "node": ">= 10.14.2" + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-debugger-ui": { - "version": "14.0.0-alpha.11", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0-alpha.11.tgz", - "integrity": "sha512-0wCNQxhCniyjyMXgR1qXliY180y/2QbvoiYpp2MleGQADr5M1b8lgI4GoyADh5kE+kX3VL0ssjgyxpmbpCD86A==", + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", "dev": true, - "peer": true, "dependencies": { - "serve-static": "^1.13.1" + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-server-api": { - "version": "14.0.0-alpha.11", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0-alpha.11.tgz", - "integrity": "sha512-I7YeYI7S5wSxnQAqeG8LNqhT99FojiGIk87DU0vTp6U8hIMLcA90fUuBAyJY38AuQZ12ZJpGa8ObkhIhWzGkvg==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], "dev": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-debugger-ui": "14.0.0-alpha.11", - "@react-native-community/cli-tools": "14.0.0-alpha.11", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^6.2.3" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-tools": { - "version": "14.0.0-alpha.11", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0-alpha.11.tgz", - "integrity": "sha512-HQCfVnX9aqRdKdLxmQy4fUAUo+YhNGlBV7ZjOayPbuEGWJ4RN+vSy0Cawk7epo7hXd6vKzc7P7y3HlU6Kxs7+w==", - "dev": true, - "peer": true, - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true - }, - "node_modules/@react-native/community-cli-plugin/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "dev": true, - "peer": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.2.tgz", - "integrity": "sha512-qIC6mrlG8RQOPaYLZQiJwqnPchAVGnHWcVDeQxPMPLkM/D5+PC8tuKWYOwgLcEau3RZlgz7QQNk31Qj2/OJG6Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.2.tgz", - "integrity": "sha512-fTC5m2uVjYp1XPaIJBFgscnQjPdGVsl96z/RfLgXDq0HBffyqbg29ttx6yTCx7lIa9Gdvf6nKQom+e+Oa4izSw==", - "dev": true, - "peer": true, - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.75.2", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.2.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "selfsigned": "^2.4.1", - "serve-static": "^1.13.1", - "ws": "^6.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "node_modules/@react-native/dev-middleware/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dev": true, - "peer": true, - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "dev": true, - "peer": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.2.tgz", - "integrity": "sha512-AELeAOCZi3B2vE6SeN+mjpZjjqzqa76yfFBB3L3f3NWiu4dm/YClTGOj+5IVRRgbt8LDuRImhDoaj7ukheXr4Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/js-polyfills": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.2.tgz", - "integrity": "sha512-AtLd3mbiE+FXK2Ru3l2NFOXDhUvzdUsCP4qspUw0haVaO/9xzV97RVD2zz0lur2f/LmZqQ2+KXyYzr7048b5iw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.2.tgz", - "integrity": "sha512-EygglCCuOub2sZ00CSIiEekCXoGL2XbOC6ssOB47M55QKvhdPG/0WBQXvmOmiN42uZgJK99Lj749v4rB0PlPIQ==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.75.2", - "hermes-parser": "0.22.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/normalize-colors": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.2.tgz", - "integrity": "sha512-nPwWJFtsqNFS/qSG9yDOiSJ64mjG7RCP4X/HXFfyWzCM1jq49h/DYBdr+c3e7AvTKGIdy0gGT3vgaRUHZFVdUQ==", - "dev": true, - "peer": true - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.75.2", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.2.tgz", - "integrity": "sha512-pD5SVCjxc8k+JdoyQ+IlulBTEqJc3S4KUKsmv5zqbNCyETB0ZUvd4Su7bp+lLF6ALxx6KKmbGk8E3LaWEjUFFQ==", - "dev": true, - "peer": true, - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": "^18.2.6", - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dev": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true, - "peer": true - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true, - "peer": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "peer": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", - "dev": true - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "node_modules/@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", - "dev": true - }, - "node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "node_modules/@types/nise": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.1.tgz", - "integrity": "sha512-LWDwHYO1C3YPpIQWXHeXAVih2nLsgN1Q5RamkYZRIZYfsz8HGNRji8vNhHs54LjcSgVx6AJC/6n/Q3Tn+fUb3g==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.17.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", - "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", - "dev": true - }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true, - "peer": true - }, - "node_modules/@types/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", - "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/coverage-istanbul": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.1.9.tgz", - "integrity": "sha512-vdYE4FkC/y2lxcN3Dcj54Bw+ericmDwiex0B8LV5F/YNYEYP1mgVwhPnHwWGAXu38qizkjOuyczKbFTALfzFKw==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@istanbuljs/schema": "^0.1.3", - "debug": "^4.3.7", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-instrument": "^6.0.3", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magicast": "^0.3.5", - "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "2.1.9" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@vitest/coverage-istanbul/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@vitest/coverage-istanbul/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vitest/coverage-istanbul/node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@vitest/expect/node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/expect/node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/expect/node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 16" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/expect/node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/expect/node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/expect/node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 14.16" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/mocker/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/mocker/node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "dev": true, - "license": "MIT", + "peer": true, "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@hapi/hoek": "^9.0.0" } }, - "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "peer": true }, - "node_modules/@vitest/utils/node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "dev": true, - "license": "MIT" + "peer": true }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "type-detect": "4.0.8" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, + "peer": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, + "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "type-detect": "4.0.8" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", "dev": true, "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "node_modules/@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, "dependencies": { - "@xtuc/long": "4.2.2" + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } + "node_modules/@types/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "dev": true }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.14", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", + "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@types/node": "*" } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "node_modules/@types/eslint": { + "version": "8.44.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", + "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true, + "peer": true }, - "node_modules/abort-controller": { + "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, "peer": true, "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, + "peer": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "@types/istanbul-lib-report": "*" } }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "node_modules/@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "node_modules/@types/nise": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.1.tgz", + "integrity": "sha512-LWDwHYO1C3YPpIQWXHeXAVih2nLsgN1Q5RamkYZRIZYfsz8HGNRji8vNhHs54LjcSgVx6AJC/6n/Q3Tn+fUb3g==", + "dev": true }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } + "node_modules/@types/node": { + "version": "18.17.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", + "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", + "dev": true }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dev": true, + "peer": true, "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "@types/node": "*" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", "dev": true, "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/node": "*" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@types/semver": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "peer": true }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/@types/ua-parser-js": { + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" } }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true, "peer": true }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, - "peer": true, "dependencies": { - "type-fest": "^0.21.3" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, - "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, - "peer": true, "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, - "peer": true, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, - "peer": true, "dependencies": { - "ansi-regex": "^4.1.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@vitest/coverage-istanbul": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.1.9.tgz", + "integrity": "sha512-vdYE4FkC/y2lxcN3Dcj54Bw+ericmDwiex0B8LV5F/YNYEYP1mgVwhPnHwWGAXu38qizkjOuyczKbFTALfzFKw==", "dev": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@istanbuljs/schema": "^0.1.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-instrument": "^6.0.3", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magicast": "^0.3.5", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "2.1.9" } }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "node_modules/@vitest/coverage-istanbul/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "peer": true + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "node_modules/@vitest/coverage-istanbul/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, + "license": "MIT", "dependencies": { - "default-require-extensions": "^3.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@vitest/coverage-istanbul/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "node_modules/@vitest/coverage-istanbul/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, - "peer": true + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, "engines": { - "node": "*" + "node": ">=10" } }, - "node_modules/ast-types": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", - "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, - "peer": true, "dependencies": { - "tslib": "^2.0.1" + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "node_modules/@vitest/coverage-istanbul/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=4" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "node_modules/@vitest/coverage-istanbul/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "peer": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "license": "MIT" }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "node_modules/@vitest/coverage-istanbul/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "peer": true, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "node_modules/@vitest/coverage-istanbul/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@vitest/expect/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">=12" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "node_modules/@vitest/expect/node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=12" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "node_modules/@vitest/expect/node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "license": "MIT", + "engines": { + "node": ">= 16" } }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "node_modules/@vitest/expect/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "peer": true, - "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "node_modules/@vitest/expect/node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, - "peer": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/@vitest/expect/node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "vite": { + "optional": true } - ], - "peer": true + } }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "node_modules/@vitest/mocker/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" - } + "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "tinyrainbow": "^1.2.0" }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "node_modules/@vitest/utils/node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" }, - "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, - "node_modules/browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "dependencies": { - "https-proxy-agent": "^2.2.1" + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" } }, - "node_modules/browserstack-local": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.4.tgz", - "integrity": "sha512-OueHCaQQutO+Fezg+ZTieRn+gdV+JocLjiAQ8nYecu08GhIt3ms79cDHfpoZmECAdoQ6OLdm7ODd+DtQzl4lrA==", + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, "dependencies": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "@xtuc/long": "4.2.2" } }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, - "peer": true, "dependencies": { - "node-int64": "^0.4.0" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true, "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, + "peer": true, "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "event-target-shim": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=6.5" } }, - "node_modules/caching-transform/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/caching-transform/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/caching-transform/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "acorn": "bin/acorn" }, "engines": { - "node": ">= 0.4" + "node": ">=0.4.0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, - "peer": true, "dependencies": { - "callsites": "^2.0.0" + "debug": "4" }, "engines": { - "node": ">=4" + "node": ">= 6.0.0" } }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "peer": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { - "caller-callsite": "^2.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "engines": { - "node": ">=6" + "peerDependencies": { + "ajv": "^6.9.1" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "dev": true, + "peer": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true, "engines": { "node": ">=6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", - "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chai": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", - "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", "dev": true, + "peer": true, "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/ansi-fragments/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "peer": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "node_modules/ansi-fragments/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">=8" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "peer": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=12.13.0" + "node": ">= 8" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", "dev": true, - "engines": { - "node": ">=6.0" - } + "peer": true }, - "node_modules/chromium-edge-launcher": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", - "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, - "peer": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/chromium-edge-launcher/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "node_modules/array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "engines": { "node": ">=8" } }, - "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "peer": true }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", "dev": true, "peer": true, "dependencies": { - "restore-cursor": "^3.1.0" + "tslib": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true, "peer": true, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", "dev": true, "peer": true, - "engines": { - "node": ">=0.8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", "dev": true, "peer": true, "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "peer": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "peer": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true, - "peer": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "dev": true, + "peer": true, "dependencies": { - "delayed-stream": "~1.0.0" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, - "engines": { - "node": ">= 0.8" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true, - "peer": true - }, - "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", "dev": true, "peer": true, - "engines": { - "node": "^12.20.0 || >=14" + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, "engines": { - "node": ">= 0.8.0" + "node": "^4.5.0 || >= 5.9" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, - "peer": true, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "peer": true, "dependencies": { - "ms": "2.0.0" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "peer": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, + "license": "MIT", "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/connect/node_modules/debug": { + "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", @@ -7382,2035 +5843,1950 @@ "ms": "2.0.0" } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "peer": true, "dependencies": { - "browserslist": "^4.23.3" + "safer-buffer": ">= 2.1.2 < 3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "peer": true + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "peer": true, "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/cosmiconfig/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true - }, - "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true }, - "node_modules/coveralls-next": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", - "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "form-data": "4.0.0", - "js-yaml": "4.1.0", - "lcov-parse": "1.0.0", - "log-driver": "1.2.7", - "minimist": "1.2.7" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { - "coveralls": "bin/coveralls.js" + "browserslist": "cli.js" }, "engines": { - "node": ">=14" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/coveralls-next/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/coveralls-next/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", "dev": true, "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "https-proxy-agent": "^2.2.1" } }, - "node_modules/coveralls-next/node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "node_modules/browserstack-local": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.4.tgz", + "integrity": "sha512-OueHCaQQutO+Fezg+ZTieRn+gdV+JocLjiAQ8nYecu08GhIt3ms79cDHfpoZmECAdoQ6OLdm7ODd+DtQzl4lrA==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" + "es6-promisify": "^5.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 4.0.0" } }, - "node_modules/create-jest/node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "ms": "^2.1.1" } }, - "node_modules/create-jest/node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, - "peer": true, "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "agent-base": "^4.3.0", + "debug": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "node": ">= 4.5.0" } }, - "node_modules/create-jest/node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "peer": true, "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node-int64": "^0.4.0" } }, - "node_modules/create-jest/node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "peer": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/create-jest/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, - "node_modules/create-jest/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, - "optional": true, - "peer": true, "engines": { - "node": ">=0.3.1" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/create-jest/node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">= 0.8" } }, - "node_modules/create-jest/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=8" } }, - "node_modules/create-jest/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, - "peer": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/create-jest/node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "semver": "^6.0.0" }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "engines": { + "node": ">=8" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "node_modules/caching-transform/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "optional": true, - "peer": true + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=4.0" + "node": ">= 0.4" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "dev": true, - "peer": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress-response": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", - "integrity": "sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==", + "peer": true, "dependencies": { - "mimic-response": "^3.1.0" + "callsites": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", "dev": true, "peer": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } + "engines": { + "node": ">=4" } }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", "dev": true, + "peer": true, "dependencies": { - "type-detect": "^4.0.0" + "caller-callsite": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/caniuse-lite": { + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "dev": true, - "peer": true, "dependencies": { - "clone": "^1.0.2" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=0.4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "dev": true, - "peer": true - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", "dev": true, "engines": { - "node": ">= 0.8" + "node": "*" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "dev": true, "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, "engines": { - "node": ">=8" + "node": ">=12.13.0" } }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, "engines": { - "node": ">=0.3.1" + "node": ">=6.0" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", "dev": true, "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, - "dependencies": { - "path-type": "^4.0.0" + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "peer": true, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, - "license": "MIT", + "peer": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "restore-cursor": "^3.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.12", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz", - "integrity": "sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "peer": true, "engines": { - "node": ">=12" + "node": ">=6" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - }, + "peer": true, "engines": { - "node": ">=10.2.0" + "node": ">=0.8" } }, - "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, + "peer": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, "engines": { - "node": ">=10.0.0" + "node": ">=6" } }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10.13.0" + "node": ">=7.0.0" } }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } + "peer": true }, - "node_modules/envinfo": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", - "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "peer": true, - "bin": { - "envinfo": "dist/cli.js" + "dependencies": { + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=4" + "node": ">= 0.8" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", "dev": true, - "peer": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } + "peer": true }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "peer": true, - "dependencies": { - "stackframe": "^1.3.4" + "engines": { + "node": "^12.20.0 || >=14" } }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "peer": true, "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" + "mime-db": ">= 1.43.0 < 2" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "dev": true, - "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "dev": true, - "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.4" + "node": ">= 0.8" } }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", + "peer": true, "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" + "ms": "2.0.0" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, "dependencies": { - "es6-promise": "^4.0.3" + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" } }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "node_modules/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", "dev": true, + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", - "@humanwhocodes/config-array": "^0.11.11", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "browserslist": "^4.23.3" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "peer": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, "dependencies": { - "get-stdin": "^6.0.0" - }, - "bin": { - "eslint-config-prettier-check": "bin/cli.js" + "object-assign": "^4", + "vary": "^1" }, - "peerDependencies": { - "eslint": ">=3.14.1" + "engines": { + "node": ">= 0.10" } }, - "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "peer": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" + "typescript": ">=4.9.5" }, "peerDependenciesMeta": { - "eslint-config-prettier": { + "typescript": { "optional": true } } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "peer": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=8.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/coveralls-next": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", + "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "dependencies": { + "form-data": "4.0.0", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.7" }, - "funding": { - "url": "https://opencollective.com/eslint" + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=14" } }, - "node_modules/eslint/node_modules/argparse": { + "node_modules/coveralls-next/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/coveralls-next/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://opencollective.com/eslint" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/coveralls-next/node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true, - "engines": { - "node": ">=4.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "dependencies": { - "is-glob": "^4.0.3" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=10.13.0" + "node": ">= 8" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true, + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "argparse": "^2.0.1" + "ms": "2.1.2" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", + "integrity": "sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==", "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "mimic-response": "^3.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "dependencies": { + "type-detect": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, + "peer": true, "engines": { - "node": ">=0.10" + "node": ">=0.10.0" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, "engines": { - "node": ">=4.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "peer": true, "dependencies": { - "estraverse": "^5.2.0" + "clone": "^1.0.2" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { - "node": ">=4.0" + "node": ">=0.4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "dev": true, + "peer": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">= 0.8" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.3.1" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "peer": true, + "esutils": "^2.0.2" + }, "engines": { - "node": ">=6" + "node": ">=6.0.0" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", "dev": true, - "engines": { - "node": ">=0.8.x" + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/exit": { + "node_modules/duplexer": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz", + "integrity": "sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, - "peer": true, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, - "peer": true, "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.2.0" } }, - "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": ">=10.0.0" } }, - "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true, - "peer": true - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=8.6.0" + "node": ">=10.13.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } }, - "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "node_modules/envinfo": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], "peer": true, - "dependencies": { - "strnum": "^1.0.5" - }, "bin": { - "fxparser": "src/cli/cli.js" + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" } }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, + "peer": true, "dependencies": { - "reusify": "^1.0.4" + "is-arrayish": "^0.2.1" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "dev": true, "peer": true, "dependencies": { - "bser": "2.1.1" + "stackframe": "^1.3.4" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", "dev": true, + "peer": true, "dependencies": { - "flat-cache": "^3.0.4" + "accepts": "~1.3.7", + "escape-html": "~1.0.3" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.8" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "dev": true, "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, "dependencies": { - "ee-first": "1.1.1" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", "dev": true, "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" + "get-stdin": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { - "semver": "bin/semver.js" + "eslint-config-prettier-check": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=3.14.1" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "prettier-linter-helpers": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "bin": { - "flat": "cli.js" + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" } }, - "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, "engines": { - "node": ">=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/flow-enums-runtime": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", - "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "dev": true, - "peer": true - }, - "node_modules/flow-parser": { - "version": "0.244.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.244.0.tgz", - "integrity": "sha512-Dkc88m5k8bx1VvHTO9HEJ7tvMcSb3Zvcv1PY4OHK7pHdtdY2aUjhmPy6vpjVJ2uUUOIybRlb91sXE8g4doChtA==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=0.4.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], "engines": { "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } } }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=8.0.0" + "node": ">=10.13.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "argparse": "^2.0.1" }, - "engines": { - "node": ">= 6" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", - "deprecated": "This package is unmaintained. Use @sinonjs/formatio instead", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "samsam": "1.x" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "peer": true, + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">= 0.6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { - "null-check": "^1.0.0" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10" } }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=4.0" } }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { - "node": ">= 4.0.0" + "node": ">=4.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=0.10.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peer": true, + "engines": { + "node": ">= 0.6" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", "dev": true, - "engines": { - "node": ">=6.9.0" + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, + "peer": true, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=6" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "engines": { - "node": "*" + "node": ">=0.8.x" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "license": "MIT", + "peer": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", "dev": true, - "license": "MIT", + "peer": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "peer": true, "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "strnum": "^1.0.5" }, - "engines": { - "node": ">= 0.4" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, - "engines": { - "node": ">=4" + "dependencies": { + "reusify": "^1.0.4" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "bser": "2.1.1" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">= 6" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/happy-dom": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.6.0.tgz", - "integrity": "sha512-Zz5S9sog8a3p8XYZbO+eI1QMOAvCNnIoyrH8A8MLX+X2mJrzADTy+kdETmc4q+uD9AGAvQYGn96qBAn2RAciKw==", + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, "dependencies": { - "webidl-conversions": "^7.0.0", - "whatwg-mimetype": "^3.0.0" + "ee-first": "1.1.1" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.8" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.6" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, "engines": { "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" + "semver": "^6.0.0" }, "engines": { "node": ">=8" @@ -9419,117 +7795,154 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "engines": { - "node": ">=8" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, "bin": { - "he": "bin/he" + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" } }, - "node_modules/hermes-estree": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.22.0.tgz", - "integrity": "sha512-FLBt5X9OfA8BERUdc6aZS36Xz3rRuB0Y/mfocSADWEJfomc1xfene33GdyAmtTkKTBXTN/EgAy+rjTKkkZJHlw==", + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", "dev": true, "peer": true }, - "node_modules/hermes-parser": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.22.0.tgz", - "integrity": "sha512-gn5RfZiEXCsIWsFGsKiykekktUoh0PdFWYocXsUdZIyWSckT6UIyPcyyUIPSR3kpnELWeK3n3ztAse7Mat6PSA==", + "node_modules/flow-parser": { + "version": "0.244.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.244.0.tgz", + "integrity": "sha512-Dkc88m5k8bx1VvHTO9HEJ7tvMcSb3Zvcv1PY4OHK7pHdtdY2aUjhmPy6vpjVJ2uUUOIybRlb91sXE8g4doChtA==", "dev": true, "peer": true, - "dependencies": { - "hermes-estree": "0.22.0" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { - "node": ">= 0.8" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" }, "engines": { "node": ">=8.0.0" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", + "deprecated": "This package is unmaintained. Use @sinonjs/formatio instead", + "dev": true, + "dependencies": { + "samsam": "1.x" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "peer": true, "engines": { - "node": ">=10.17.0" + "node": ">= 0.6" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", "dev": true, "funding": [ { @@ -9544,288 +7957,217 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "peer": true - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", - "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", - "dev": true, - "peer": true, - "dependencies": { - "queue": "6.0.2" - }, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } + ] }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "null-check": "^1.0.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, - "peer": true, "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.0.0" + "node": ">=6 <7 || >=8" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "peer": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/fs-extra/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 4.0.0" } }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "peer": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "peer": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "engines": { - "node": ">=8" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, - "peer": true, "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, - "peer": true, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "peer": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "peer": true, "dependencies": { - "isobject": "^3.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { - "@types/estree": "*" + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { "node": ">=8" }, @@ -9833,17 +8175,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, "engines": { "node": ">=10" }, @@ -9851,1071 +8195,715 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/happy-dom": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.6.0.tgz", + "integrity": "sha512-Zz5S9sog8a3p8XYZbO+eI1QMOAvCNnIoyrH8A8MLX+X2mJrzADTy+kdETmc4q+uD9AGAvQYGn96qBAn2RAciKw==", "dev": true, - "peer": true, "dependencies": { - "is-docker": "^2.0.0" + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, - "engines": { - "node": ">= 8.0.0" + "dependencies": { + "function-bind": "^1.1.1" }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/istanbul-lib-processinfo/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "bin": { - "uuid": "dist/bin/uuid" + "he": "bin/he" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/hermes-estree": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.22.0.tgz", + "integrity": "sha512-FLBt5X9OfA8BERUdc6aZS36Xz3rRuB0Y/mfocSADWEJfomc1xfene33GdyAmtTkKTBXTN/EgAy+rjTKkkZJHlw==", "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } + "peer": true }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "node_modules/hermes-parser": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.22.0.tgz", + "integrity": "sha512-gn5RfZiEXCsIWsFGsKiykekktUoh0PdFWYocXsUdZIyWSckT6UIyPcyyUIPSR3kpnELWeK3n3ztAse7Mat6PSA==", "dev": true, + "peer": true, "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" + "hermes-estree": "0.22.0" } }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, - "peer": true, "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 6" } }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "peer": true, - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.17.0" } }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 4" } }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "node_modules/image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", "dev": true, "peer": true, "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "queue": "6.0.2" }, "bin": { - "jest": "bin/jest.js" + "image-size": "bin/image-size.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=16.x" } }, - "node_modules/jest-cli/node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-cli/node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "peer": true, - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "node": ">=0.8.19" } }, - "node_modules/jest-cli/node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "peer": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-cli/node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "peer": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/jest-cli/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, - "node_modules/jest-cli/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, - "optional": true, "peer": true, - "engines": { - "node": ">=0.3.1" + "dependencies": { + "loose-envify": "^1.0.0" } }, - "node_modules/jest-cli/node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "peer": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "binary-extensions": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">=8" } }, - "node_modules/jest-cli/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "has": "^1.0.3" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", "dev": true, "peer": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-cli/node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "optional": true, "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "is-docker": "cli.js" }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "engines": { + "node": ">=8" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "peer": true, "dependencies": { - "detect-newline": "^3.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.12.0" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "peer": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "peer": true, - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "peer": true, "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "isobject": "^3.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, - "peer": true, "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@types/estree": "*" } }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "node_modules/is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", + "dev": true + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "peer": true, "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" + "node": ">=10" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "peer": true, "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "is-docker": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, - "peer": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" } }, - "node_modules/jest-resolve/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=0.10.0" } }, - "node_modules/jest-resolve/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "peer": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, - "peer": true, "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "append-transform": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runner/node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runner/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, - "peer": true + "bin": { + "uuid": "dist/bin/uuid" + } }, - "node_modules/jest-runner/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=10" } }, - "node_modules/jest-runner/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "peer": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, - "peer": true, "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest-runtime/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true - }, - "node_modules/jest-runtime/node_modules/jest-haste-map": { + "node_modules/jest-environment-node": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "peer": true, "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" } }, - "node_modules/jest-runtime/node_modules/jest-regex-util": { + "node_modules/jest-get-type": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/transform": { + "node_modules/jest-message-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "peer": true, "dependencies": { - "@babel/core": "^7.11.6", + "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "pretty-format": "^29.7.0", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "stack-utils": "^2.0.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true - }, - "node_modules/jest-snapshot/node_modules/jest-haste-map": { + "node_modules/jest-mock": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "jest-util": "^29.7.0" }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-snapshot/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -10925,6 +8913,7 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -10968,26 +8957,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "peer": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -11543,12 +9512,6 @@ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -13790,23 +11753,6 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "peer": true - }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -14292,29 +12238,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "peer": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -14324,16 +12247,6 @@ "node": ">=4" } }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -15217,20 +13130,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "peer": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -15696,49 +13595,6 @@ "node": ">=0.6" } }, - "node_modules/ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, "node_modules/ts-loader": { "version": "9.4.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", @@ -15758,15 +13614,6 @@ "webpack": "^5.0.0" } }, - "node_modules/ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.5" - } - }, "node_modules/ts-node": { "version": "8.10.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", @@ -16038,9 +13885,9 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -16049,36 +13896,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -16638,20 +14455,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -16735,6 +14538,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "peer": true, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index d67a034bd..40bc9df11 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ }, "license": "Apache-2.0", "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, "keywords": [ "optimizely" @@ -94,7 +94,7 @@ "decompress-response": "^7.0.0", "json-schema": "^0.4.0", "murmurhash": "^2.0.1", - "uuid": "^9.0.1" + "uuid": "^10.0.0" }, "devDependencies": { "@react-native-async-storage/async-storage": "^2", @@ -106,7 +106,7 @@ "@types/nise": "^1.4.0", "@types/node": "^18.7.18", "@types/ua-parser-js": "^0.7.36", - "@types/uuid": "^9.0.7", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.33.0", "@typescript-eslint/parser": "^5.33.0", "@vitest/coverage-istanbul": "^2.0.5", @@ -136,9 +136,7 @@ "rollup-plugin-terser": "^5.3.0", "rollup-plugin-typescript2": "^0.27.1", "sinon": "^2.3.1", - "ts-jest": "^29.1.2", "ts-loader": "^9.3.1", - "ts-mockito": "^2.6.1", "ts-node": "^8.10.2", "tsconfig-paths": "^4.2.0", "tslib": "^2.4.0", From 2937fe0f240b93602eb168ad7b68d97fc99e9d36 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 22 May 2025 00:03:17 +0600 Subject: [PATCH 171/200] [FSSDK-11510] add validation to factories (#1060) also updated createInstance to throw in case of validation errors --- lib/client_factory.spec.ts | 61 ++++++++++++ lib/client_factory.ts | 99 ++++++++----------- lib/core/decision_service/index.tests.js | 2 +- lib/entrypoint.test-d.ts | 2 +- lib/entrypoint.universal.test-d.ts | 2 +- lib/error/error_notifier_factory.spec.ts | 33 +++++++ lib/error/error_notifier_factory.ts | 18 +++- .../event_dispatcher_factory.ts | 3 + .../event_processor_factory.browser.spec.ts | 15 +-- .../event_processor_factory.browser.ts | 3 +- .../event_processor_factory.node.spec.ts | 11 +-- .../event_processor_factory.node.ts | 2 +- ...ent_processor_factory.react_native.spec.ts | 10 +- .../event_processor_factory.react_native.ts | 2 +- .../event_processor_factory.spec.ts | 85 ++++++++++++++++ .../event_processor_factory.ts | 51 +++++++++- .../event_processor_factory.universal.ts | 2 +- .../forwarding_event_processor.spec.ts | 16 +-- .../forwarding_event_processor.ts | 8 +- lib/index.browser.tests.js | 22 ++--- lib/index.browser.ts | 2 +- lib/index.node.tests.js | 24 +++-- lib/index.node.ts | 2 +- lib/index.react_native.spec.ts | 28 +++--- lib/index.react_native.ts | 2 +- lib/index.universal.ts | 2 +- lib/logging/logger_factory.spec.ts | 41 +++++++- lib/logging/logger_factory.ts | 26 ++++- lib/message/error_message.ts | 3 - lib/odp/odp_manager_factory.spec.ts | 44 ++++++++- lib/odp/odp_manager_factory.ts | 35 ++++++- lib/odp/odp_manager_factory.universal.ts | 2 + lib/optimizely/index.spec.ts | 15 ++- lib/optimizely/index.tests.js | 4 +- lib/optimizely_user_context/index.tests.js | 4 +- lib/project_config/config_manager_factory.ts | 13 ++- .../config_manager_factory.universal.ts | 5 +- lib/utils/config_validator/index.ts | 30 +----- .../request_handler_validator.ts | 28 ++++++ lib/vuid/vuid_manager.spec.ts | 2 +- lib/vuid/vuid_manager_factory.ts | 6 +- 41 files changed, 554 insertions(+), 211 deletions(-) create mode 100644 lib/client_factory.spec.ts create mode 100644 lib/error/error_notifier_factory.spec.ts create mode 100644 lib/utils/http_request_handler/request_handler_validator.ts diff --git a/lib/client_factory.spec.ts b/lib/client_factory.spec.ts new file mode 100644 index 000000000..1aa09bda0 --- /dev/null +++ b/lib/client_factory.spec.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; + +import { getOptimizelyInstance } from './client_factory'; +import { createStaticProjectConfigManager } from './project_config/config_manager_factory'; +import Optimizely from './optimizely'; + +describe('getOptimizelyInstance', () => { + it('should throw if the projectConfigManager is not a valid ProjectConfigManager', () => { + expect(() => getOptimizelyInstance({ + projectConfigManager: undefined as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: null as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: 'abc' as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: 123 as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: {} as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + }); + + it('should return an instance of Optimizely if a valid projectConfigManager is provided', () => { + const optimizelyInstance = getOptimizelyInstance({ + projectConfigManager: createStaticProjectConfigManager({ + datafile: '{}', + }), + requestHandler: {} as any, + }); + + expect(optimizelyInstance).toBeInstanceOf(Optimizely); + }); +}); diff --git a/lib/client_factory.ts b/lib/client_factory.ts index 42c650fd6..7307075cf 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -13,11 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { LoggerFacade } from "./logging/logger"; import { Client, Config } from "./shared_types"; -import { Maybe } from "./utils/type"; -import configValidator from './utils/config_validator'; import { extractLogger } from "./logging/logger_factory"; import { extractErrorNotifier } from "./error/error_notifier_factory"; import { extractConfigManager } from "./project_config/config_manager_factory"; @@ -35,61 +31,50 @@ export type OptimizelyFactoryConfig = Config & { requestHandler: RequestHandler; } -export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client | null => { - let logger: Maybe<LoggerFacade>; - - try { - logger = config.logger ? extractLogger(config.logger) : undefined; - - configValidator.validate(config); - - const { - clientEngine, - clientVersion, - jsonSchemaValidator, - userProfileService, - userProfileServiceAsync, - defaultDecideOptions, - disposable, - requestHandler, - } = config; - - const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined; - - const projectConfigManager = extractConfigManager(config.projectConfigManager); - const eventProcessor = config.eventProcessor ? extractEventProcessor(config.eventProcessor) : undefined; - const odpManager = config.odpManager ? extractOdpManager(config.odpManager) : undefined; - const vuidManager = config.vuidManager ? extractVuidManager(config.vuidManager) : undefined; +export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client => { + const { + clientEngine, + clientVersion, + jsonSchemaValidator, + userProfileService, + userProfileServiceAsync, + defaultDecideOptions, + disposable, + requestHandler, + } = config; + + const projectConfigManager = extractConfigManager(config.projectConfigManager); + const eventProcessor = extractEventProcessor(config.eventProcessor); + const odpManager = extractOdpManager(config.odpManager); + const vuidManager = extractVuidManager(config.vuidManager); + const errorNotifier = extractErrorNotifier(config.errorNotifier); + const logger = extractLogger(config.logger); - const cmabClient = new DefaultCmabClient({ - requestHandler, - }); + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); - const cmabService = new DefaultCmabService({ - cmabClient, - cmabCache: new InMemoryLruCache<CmabCacheValue>(DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT), - }); + const cmabService = new DefaultCmabService({ + cmabClient, + cmabCache: new InMemoryLruCache<CmabCacheValue>(DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT), + }); - const optimizelyOptions = { - cmabService, - clientEngine: clientEngine || JAVASCRIPT_CLIENT_ENGINE, - clientVersion: clientVersion || CLIENT_VERSION, - jsonSchemaValidator, - userProfileService, - userProfileServiceAsync, - defaultDecideOptions, - disposable, - logger, - errorNotifier, - projectConfigManager, - eventProcessor, - odpManager, - vuidManager, - }; + const optimizelyOptions = { + cmabService, + clientEngine: clientEngine || JAVASCRIPT_CLIENT_ENGINE, + clientVersion: clientVersion || CLIENT_VERSION, + jsonSchemaValidator, + userProfileService, + userProfileServiceAsync, + defaultDecideOptions, + disposable, + logger, + errorNotifier, + projectConfigManager, + eventProcessor, + odpManager, + vuidManager, + }; - return new Optimizely(optimizelyOptions); - } catch (e) { - logger?.error(e); - return null; - } + return new Optimizely(optimizelyOptions); } diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 98f9dde70..cdc4dc7c7 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -25,7 +25,7 @@ import { LOG_LEVEL, DECISION_SOURCES, } from '../../utils/enums'; -import { getForwardingEventProcessor } from '../../event_processor/forwarding_event_processor'; +import { getForwardingEventProcessor } from '../../event_processor/event_processor_factory'; import { createNotificationCenter } from '../../notification_center'; import Optimizely from '../../optimizely'; import OptimizelyUserContext from '../../optimizely_user_context'; diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts index 3dd2f3c06..366889ea8 100644 --- a/lib/entrypoint.test-d.ts +++ b/lib/entrypoint.test-d.ts @@ -59,7 +59,7 @@ import { Maybe } from './utils/type'; export type Entrypoint = { // client factory - createInstance: (config: Config) => Client | null; + createInstance: (config: Config) => Client; // config manager related exports createStaticProjectConfigManager: (config: StaticConfigManagerConfig) => OpaqueConfigManager; diff --git a/lib/entrypoint.universal.test-d.ts b/lib/entrypoint.universal.test-d.ts index 2fa1891d4..184583a35 100644 --- a/lib/entrypoint.universal.test-d.ts +++ b/lib/entrypoint.universal.test-d.ts @@ -55,7 +55,7 @@ import { UniversalOdpManagerOptions } from './odp/odp_manager_factory.universal' export type UniversalEntrypoint = { // client factory - createInstance: (config: UniversalConfig) => Client | null; + createInstance: (config: UniversalConfig) => Client; // config manager related exports createStaticProjectConfigManager: (config: StaticConfigManagerConfig) => OpaqueConfigManager; diff --git a/lib/error/error_notifier_factory.spec.ts b/lib/error/error_notifier_factory.spec.ts new file mode 100644 index 000000000..556d7f2af --- /dev/null +++ b/lib/error/error_notifier_factory.spec.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import { createErrorNotifier } from './error_notifier_factory'; + +describe('createErrorNotifier', () => { + it('should throw errors for invalid error handlers', () => { + expect(() => createErrorNotifier(null as any)).toThrow('Invalid error handler'); + expect(() => createErrorNotifier(undefined as any)).toThrow('Invalid error handler'); + + + expect(() => createErrorNotifier('abc' as any)).toThrow('Invalid error handler'); + expect(() => createErrorNotifier(123 as any)).toThrow('Invalid error handler'); + + expect(() => createErrorNotifier({} as any)).toThrow('Invalid error handler'); + + expect(() => createErrorNotifier({ handleError: 'abc' } as any)).toThrow('Invalid error handler'); + }); +}); diff --git a/lib/error/error_notifier_factory.ts b/lib/error/error_notifier_factory.ts index 970723591..994564f1a 100644 --- a/lib/error/error_notifier_factory.ts +++ b/lib/error/error_notifier_factory.ts @@ -14,21 +14,35 @@ * limitations under the License. */ import { errorResolver } from "../message/message_resolver"; +import { Maybe } from "../utils/type"; import { ErrorHandler } from "./error_handler"; import { DefaultErrorNotifier } from "./error_notifier"; +export const INVALID_ERROR_HANDLER = 'Invalid error handler'; + const errorNotifierSymbol = Symbol(); export type OpaqueErrorNotifier = { [errorNotifierSymbol]: unknown; }; +const validateErrorHandler = (errorHandler: ErrorHandler) => { + if (!errorHandler || typeof errorHandler !== 'object' || typeof errorHandler.handleError !== 'function') { + throw new Error(INVALID_ERROR_HANDLER); + } +} + export const createErrorNotifier = (errorHandler: ErrorHandler): OpaqueErrorNotifier => { + validateErrorHandler(errorHandler); return { [errorNotifierSymbol]: new DefaultErrorNotifier(errorHandler, errorResolver), } } -export const extractErrorNotifier = (errorNotifier: OpaqueErrorNotifier): DefaultErrorNotifier => { - return errorNotifier[errorNotifierSymbol] as DefaultErrorNotifier; +export const extractErrorNotifier = (errorNotifier: Maybe<OpaqueErrorNotifier>): Maybe<DefaultErrorNotifier> => { + if (!errorNotifier || typeof errorNotifier !== 'object') { + return undefined; + } + + return errorNotifier[errorNotifierSymbol] as Maybe<DefaultErrorNotifier>; } diff --git a/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts b/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts index 035fb7e49..383ad8380 100644 --- a/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts +++ b/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts @@ -18,6 +18,9 @@ import { RequestHandler } from '../../utils/http_request_handler/http'; import { DefaultEventDispatcher } from './default_dispatcher'; import { EventDispatcher } from './event_dispatcher'; +import { validateRequestHandler } from '../../utils/http_request_handler/request_handler_validator'; + export const createEventDispatcher = (requestHander: RequestHandler): EventDispatcher => { + validateRequestHandler(requestHander); return new DefaultEventDispatcher(requestHander); } diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts index 475b36353..a5d2a6af3 100644 --- a/lib/event_processor/event_processor_factory.browser.spec.ts +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -19,13 +19,6 @@ vi.mock('./default_dispatcher.browser', () => { return { default: {} }; }); -vi.mock('./forwarding_event_processor', () => { - const getForwardingEventProcessor = vi.fn().mockImplementation(() => { - return {}; - }); - return { getForwardingEventProcessor }; -}); - vi.mock('./event_processor_factory', async (importOriginal) => { const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; @@ -33,8 +26,11 @@ vi.mock('./event_processor_factory', async (importOriginal) => { const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); + const getForwardingEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); const original: any = await importOriginal(); - return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor }; + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor, getForwardingEventProcessor }; }); vi.mock('../utils/cache/local_storage_cache.browser', () => { @@ -50,9 +46,8 @@ import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browse import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; import { SyncPrefixStore } from '../utils/cache/store'; import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.browser'; -import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, extractEventProcessor, getForwardingEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; -import { getForwardingEventProcessor } from './forwarding_event_processor'; import browserDefaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { getOpaqueBatchEventProcessor } from './event_processor_factory'; diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts index ff53b0298..e73b8bf24 100644 --- a/lib/event_processor/event_processor_factory.browser.ts +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { EventProcessor } from './event_processor'; import { EventWithId } from './batch_event_processor'; @@ -23,6 +21,7 @@ import { BatchEventProcessorOptions, OpaqueEventProcessor, wrapEventProcessor, + getForwardingEventProcessor, } from './event_processor_factory'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts index 512865381..22b943f19 100644 --- a/lib/event_processor/event_processor_factory.node.spec.ts +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -19,11 +19,6 @@ vi.mock('./default_dispatcher.node', () => { return { default: {} }; }); -vi.mock('./forwarding_event_processor', () => { - const getForwardingEventProcessor = vi.fn().mockReturnValue({}); - return { getForwardingEventProcessor }; -}); - vi.mock('./event_processor_factory', async (importOriginal) => { const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; @@ -31,8 +26,9 @@ vi.mock('./event_processor_factory', async (importOriginal) => { const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); + const getForwardingEventProcessor = vi.fn().mockReturnValue({}); const original: any = await importOriginal(); - return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor }; + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor, getForwardingEventProcessor }; }); vi.mock('../utils/cache/async_storage_cache.react_native', () => { @@ -44,9 +40,8 @@ vi.mock('../utils/cache/store', () => { }); import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor_factory.node'; -import { getForwardingEventProcessor } from './forwarding_event_processor'; import nodeDefaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; -import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, extractEventProcessor, getForwardingEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { getOpaqueBatchEventProcessor } from './event_processor_factory'; import { AsyncStore, AsyncPrefixStore, SyncStore, SyncPrefixStore } from '../utils/cache/store'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts index cdcb533a1..b0ed4ffde 100644 --- a/lib/event_processor/event_processor_factory.node.ts +++ b/lib/event_processor/event_processor_factory.node.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; import { @@ -23,6 +22,7 @@ import { getPrefixEventStore, OpaqueEventProcessor, wrapEventProcessor, + getForwardingEventProcessor, } from './event_processor_factory'; export const DEFAULT_EVENT_BATCH_SIZE = 10; diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 6065e16de..630417a5e 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -20,12 +20,9 @@ vi.mock('./default_dispatcher.browser', () => { return { default: {} }; }); -vi.mock('./forwarding_event_processor', () => { - const getForwardingEventProcessor = vi.fn().mockReturnValue({}); - return { getForwardingEventProcessor }; -}); vi.mock('./event_processor_factory', async importOriginal => { + const getForwardingEventProcessor = vi.fn().mockReturnValue({}); const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); @@ -33,7 +30,7 @@ vi.mock('./event_processor_factory', async importOriginal => { return {}; }); const original: any = await importOriginal(); - return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor }; + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor, getForwardingEventProcessor }; }); vi.mock('../utils/cache/async_storage_cache.react_native', () => { @@ -68,9 +65,8 @@ async function mockRequireNetInfo() { } import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; -import { getForwardingEventProcessor } from './forwarding_event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; -import { EVENT_STORE_PREFIX, extractEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, extractEventProcessor, getForwardingEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { getOpaqueBatchEventProcessor } from './event_processor_factory'; import { AsyncStore, AsyncPrefixStore, SyncStore, SyncPrefixStore } from '../utils/cache/store'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts index 99019eff0..b46b594a4 100644 --- a/lib/event_processor/event_processor_factory.react_native.ts +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; import { @@ -22,6 +21,7 @@ import { getPrefixEventStore, OpaqueEventProcessor, wrapEventProcessor, + getForwardingEventProcessor, } from './event_processor_factory'; import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; import { AsyncPrefixStore } from '../utils/cache/store'; diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts index fc57a5097..31b1b62ed 100644 --- a/lib/event_processor/event_processor_factory.spec.ts +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -41,6 +41,91 @@ describe('getBatchEventProcessor', () => { MockIntervalRepeater.mockReset(); }); + it('should throw an error if provided eventDispatcher is not valid', () => { + expect(() => getBatchEventProcessor({ + eventDispatcher: undefined as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: null as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: 'abc' as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: {} as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: { dispatchEvent: 'abc' } as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + }); + + it('should throw and error if provided event store is invalid', () => { + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: 'abc' as any, + })).toThrow('Invalid event store'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: 123 as any, + })).toThrow('Invalid event store'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: {} as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: 'abc', get: 'abc', remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + const noop = () => {}; + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: noop, get: 'abc' } as any, + })).toThrow('Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: noop, get: noop, remove: 'abc' } as any, + })).toThrow('Invalid store method remove, Invalid store method getKeys'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: noop, get: noop, remove: noop, getKeys: 'abc' } as any, + })).toThrow('Invalid store method getKeys'); + }); + it('returns an instane of BatchEventProcessor if no subclass constructor is provided', () => { const options = { eventDispatcher: getMockEventDispatcher(), diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index 3fff90c9f..dd50c72f2 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -19,13 +19,19 @@ import { StartupLog } from "../service"; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; import { EventDispatcher } from "./event_dispatcher/event_dispatcher"; import { EventProcessor } from "./event_processor"; +import { ForwardingEventProcessor } from "./forwarding_event_processor"; import { BatchEventProcessor, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, EventWithId, RetryConfig } from "./batch_event_processor"; import { AsyncPrefixStore, Store, SyncPrefixStore } from "../utils/cache/store"; +import { Maybe } from "../utils/type"; +export const INVALID_EVENT_DISPATCHER = 'Invalid event dispatcher'; export const FAILED_EVENT_RETRY_INTERVAL = 20 * 1000; export const EVENT_STORE_PREFIX = 'optly_event:'; +export const INVALID_STORE = 'Invalid event store'; +export const INVALID_STORE_METHOD = 'Invalid store method %s'; + export const getPrefixEventStore = (store: Store<string>): Store<EventWithId> => { if (store.operation === 'async') { return new AsyncPrefixStore<string, EventWithId>( @@ -72,12 +78,44 @@ export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, }; } +export const validateEventDispatcher = (eventDispatcher: EventDispatcher): void => { + if (!eventDispatcher || typeof eventDispatcher !== 'object' || typeof eventDispatcher.dispatchEvent !== 'function') { + throw new Error(INVALID_EVENT_DISPATCHER); + } +} + +const validateStore = (store: any) => { + const errors = []; + if (!store || typeof store !== 'object') { + throw new Error(INVALID_STORE); + } + + for (const method of ['set', 'get', 'remove', 'getKeys']) { + if (typeof store[method] !== 'function') { + errors.push(INVALID_STORE_METHOD.replace('%s', method)); + } + } + + if (errors.length > 0) { + throw new Error(errors.join(', ')); + } +} + export const getBatchEventProcessor = ( options: BatchEventProcessorFactoryOptions, EventProcessorConstructor: typeof BatchEventProcessor = BatchEventProcessor ): EventProcessor => { const { eventDispatcher, closingEventDispatcher, retryOptions, eventStore } = options; + validateEventDispatcher(eventDispatcher); + if (closingEventDispatcher) { + validateEventDispatcher(closingEventDispatcher); + } + + if (eventStore) { + validateStore(eventStore); + } + const retryConfig: RetryConfig | undefined = retryOptions ? { maxRetries: retryOptions.maxRetries, backoffProvider: () => { @@ -142,6 +180,15 @@ export const getOpaqueBatchEventProcessor = ( return wrapEventProcessor(getBatchEventProcessor(options, EventProcessorConstructor)); } -export const extractEventProcessor = (eventProcessor: OpaqueEventProcessor): EventProcessor => { - return eventProcessor[eventProcessorSymbol] as EventProcessor; +export const extractEventProcessor = (eventProcessor: Maybe<OpaqueEventProcessor>): Maybe<EventProcessor> => { + if (!eventProcessor || typeof eventProcessor !== 'object') { + return undefined; + } + return eventProcessor[eventProcessorSymbol] as Maybe<EventProcessor>; +} + + +export function getForwardingEventProcessor(dispatcher: EventDispatcher): EventProcessor { + validateEventDispatcher(dispatcher); + return new ForwardingEventProcessor(dispatcher); } diff --git a/lib/event_processor/event_processor_factory.universal.ts b/lib/event_processor/event_processor_factory.universal.ts index 7b192f96a..0a3b2ec56 100644 --- a/lib/event_processor/event_processor_factory.universal.ts +++ b/lib/event_processor/event_processor_factory.universal.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getForwardingEventProcessor } from './forwarding_event_processor'; +import { getForwardingEventProcessor } from './event_processor_factory'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { diff --git a/lib/event_processor/forwarding_event_processor.spec.ts b/lib/event_processor/forwarding_event_processor.spec.ts index 76b69a185..65d571cb9 100644 --- a/lib/event_processor/forwarding_event_processor.spec.ts +++ b/lib/event_processor/forwarding_event_processor.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021, 2024 Optimizely + * Copyright 2021, 2024-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ */ import { expect, describe, it, vi } from 'vitest'; -import { getForwardingEventProcessor } from './forwarding_event_processor'; import { EventDispatcher } from './event_dispatcher/event_dispatcher'; import { buildLogEvent, makeEventBatch } from './event_builder/log_event'; import { createImpressionEvent } from '../tests/mock/create_event'; import { ServiceState } from '../service'; +import { ForwardingEventProcessor } from './forwarding_event_processor'; const getMockEventDispatcher = (): EventDispatcher => { return { @@ -31,7 +31,7 @@ describe('ForwardingEventProcessor', () => { it('should resolve onRunning() when start is called', async () => { const dispatcher = getMockEventDispatcher(); - const processor = getForwardingEventProcessor(dispatcher); + const processor = new ForwardingEventProcessor(dispatcher); processor.start(); await expect(processor.onRunning()).resolves.not.toThrow(); @@ -41,7 +41,7 @@ describe('ForwardingEventProcessor', () => { const dispatcher = getMockEventDispatcher(); const mockDispatch = vi.mocked(dispatcher.dispatchEvent); - const processor = getForwardingEventProcessor(dispatcher); + const processor = new ForwardingEventProcessor(dispatcher); processor.start(); await processor.onRunning(); @@ -56,7 +56,7 @@ describe('ForwardingEventProcessor', () => { it('should emit dispatch event when event is dispatched', async() => { const dispatcher = getMockEventDispatcher(); - const processor = getForwardingEventProcessor(dispatcher); + const processor = new ForwardingEventProcessor(dispatcher); processor.start(); await processor.onRunning(); @@ -75,7 +75,7 @@ describe('ForwardingEventProcessor', () => { it('should remove dispatch listener when the function returned from onDispatch is called', async() => { const dispatcher = getMockEventDispatcher(); - const processor = getForwardingEventProcessor(dispatcher); + const processor = new ForwardingEventProcessor(dispatcher); processor.start(); await processor.onRunning(); @@ -98,7 +98,7 @@ describe('ForwardingEventProcessor', () => { it('should resolve onTerminated promise when stop is called', async () => { const dispatcher = getMockEventDispatcher(); - const processor = getForwardingEventProcessor(dispatcher); + const processor = new ForwardingEventProcessor(dispatcher); processor.start(); await processor.onRunning(); @@ -110,7 +110,7 @@ describe('ForwardingEventProcessor', () => { it('should reject onRunning promise when stop is called in New state', async () => { const dispatcher = getMockEventDispatcher(); - const processor = getForwardingEventProcessor(dispatcher); + const processor = new ForwardingEventProcessor(dispatcher); expect(processor.getState()).toEqual(ServiceState.New); diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index a0587ab6a..80ce1c763 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021-2024, Optimizely + * Copyright 2021-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import { Consumer, Fn } from '../utils/type'; import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; import { sprintf } from '../utils/fns'; -class ForwardingEventProcessor extends BaseService implements EventProcessor { +export class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; private eventEmitter: EventEmitter<{ dispatch: LogEvent }>; @@ -70,7 +70,3 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { return this.eventEmitter.on('dispatch', handler); } } - -export function getForwardingEventProcessor(dispatcher: EventDispatcher): EventProcessor { - return new ForwardingEventProcessor(dispatcher); -} diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 3ea249904..28b94a9d0 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2020, 2022-2024 Optimizely + * Copyright 2016-2020, 2022-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,13 +104,11 @@ describe('javascript-sdk (Browser)', function() { mockLogger = getLogger(); sinon.stub(mockLogger, 'error'); - sinon.stub(configValidator, 'validate'); global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); }); afterEach(function() { mockLogger.error.restore(); - configValidator.validate.restore(); delete global.XMLHttpRequest; }); @@ -136,15 +134,15 @@ describe('javascript-sdk (Browser)', function() { // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); // }); - it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); - assert.doesNotThrow(function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), - logger: wrapLogger(mockLogger), - }); - }); - }); + // it('should not throw if the provided config is not valid', function() { + // configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); + // assert.doesNotThrow(function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // logger: wrapLogger(mockLogger), + // }); + // }); + // }); it('should create an instance of optimizely', function() { var optlyInstance = optimizelyFactory.createInstance({ diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 96249c6a9..4cbfc7c69 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -26,7 +26,7 @@ import { BrowserRequestHandler } from './utils/http_request_handler/request_hand * @return {Client|null} the Optimizely client object * null on error */ -export const createInstance = function(config: Config): Client | null { +export const createInstance = function(config: Config): Client { const client = getOptimizelyInstance({ ...config, requestHandler: new BrowserRequestHandler(), diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 0146fffab..8279c5110 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2020, 2022-2024 Optimizely + * Copyright 2016-2020, 2022-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,12 +49,10 @@ describe('optimizelyFactory', function() { var fakeLogger = createLogger(); beforeEach(function() { - sinon.stub(configValidator, 'validate'); sinon.stub(fakeLogger, 'error'); }); afterEach(function() { - configValidator.validate.restore(); fakeLogger.error.restore(); }); @@ -70,16 +68,16 @@ describe('optimizelyFactory', function() { // sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); // }); - it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); - assert.doesNotThrow(function() { - var optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), - logger: wrapLogger(fakeLogger), - }); - }); - // sinon.assert.calledOnce(fakeLogger.error); - }); + // it('should not throw if the provided config is not valid', function() { + // configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); + // assert.doesNotThrow(function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // logger: wrapLogger(fakeLogger), + // }); + // }); + // // sinon.assert.calledOnce(fakeLogger.error); + // }); // it('should create an instance of optimizely', function() { // var optlyInstance = optimizelyFactory.createInstance({ diff --git a/lib/index.node.ts b/lib/index.node.ts index b911d0d6a..cd12b25fb 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -25,7 +25,7 @@ import { NodeRequestHandler } from './utils/http_request_handler/request_handler * @return {Client|null} the Optimizely client object * null on error */ -export const createInstance = function(config: Config): Client | null { +export const createInstance = function(config: Config): Client { const nodeConfig = { ...config, clientEnging: config.clientEngine || NODE_CLIENT_ENGINE, diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index d091e889a..d46311278 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, 2022-2024 Optimizely + * Copyright 2019-2020, 2022-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,19 +66,19 @@ describe('javascript-sdk/react-native', () => { vi.resetAllMocks(); }); - it('should not throw if the provided config is not valid', () => { - vi.spyOn(configValidator, 'validate').mockImplementation(() => { - throw new Error('Invalid config or something'); - }); - expect(function() { - const optlyInstance = optimizelyFactory.createInstance({ - projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - logger: wrapLogger(mockLogger), - }); - }).not.toThrow(); - }); + // it('should not throw if the provided config is not valid', () => { + // vi.spyOn(configValidator, 'validate').mockImplementation(() => { + // throw new Error('Invalid config or something'); + // }); + // expect(function() { + // const optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // logger: wrapLogger(mockLogger), + // }); + // }).not.toThrow(); + // }); it('should create an instance of optimizely', () => { const optlyInstance = optimizelyFactory.createInstance({ diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index df386fa66..a556290ba 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -28,7 +28,7 @@ import { BrowserRequestHandler } from './utils/http_request_handler/request_hand * @return {Client|null} the Optimizely client object * null on error */ -export const createInstance = function(config: Config): Client | null { +export const createInstance = function(config: Config): Client { const rnConfig = { ...config, clientEngine: config.clientEngine || REACT_NATIVE_JS_CLIENT_ENGINE, diff --git a/lib/index.universal.ts b/lib/index.universal.ts index 5cc64f51e..4ef8cc20f 100644 --- a/lib/index.universal.ts +++ b/lib/index.universal.ts @@ -29,7 +29,7 @@ export type UniversalConfig = Config & { * @return {Client|null} the Optimizely client object * null on error */ -export const createInstance = function(config: UniversalConfig): Client | null { +export const createInstance = function(config: UniversalConfig): Client { return getOptimizelyInstance(config); }; diff --git a/lib/logging/logger_factory.spec.ts b/lib/logging/logger_factory.spec.ts index 6910ab67a..bc7671008 100644 --- a/lib/logging/logger_factory.spec.ts +++ b/lib/logging/logger_factory.spec.ts @@ -28,7 +28,7 @@ import { OptimizelyLogger, ConsoleLogHandler, LogLevel } from './logger'; import { createLogger, extractLogger, INFO } from './logger_factory'; import { errorResolver, infoResolver } from '../message/message_resolver'; -describe('create', () => { +describe('createLogger', () => { const MockedOptimizelyLogger = vi.mocked(OptimizelyLogger); const MockedConsoleLogHandler = vi.mocked(ConsoleLogHandler); @@ -37,6 +37,45 @@ describe('create', () => { MockedOptimizelyLogger.mockClear(); }); + it('should throw an error if the provided logHandler is not a valid LogHandler', () => { + expect(() => createLogger({ + level: INFO, + logHandler: {} as any, + })).toThrow('Invalid log handler'); + + expect(() => createLogger({ + level: INFO, + logHandler: { log: 'abc' } as any, + })).toThrow('Invalid log handler'); + + expect(() => createLogger({ + level: INFO, + logHandler: 'abc' as any, + })).toThrow('Invalid log handler'); + }); + + it('should throw an error if the level is not a valid level preset', () => { + expect(() => createLogger({ + level: null as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: undefined as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: 'abc' as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: 123 as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: {} as any, + })).toThrow('Invalid level preset'); + }); + it('should use the passed in options and a default name Optimizely', () => { const mockLogHandler = { log: vi.fn() }; diff --git a/lib/logging/logger_factory.ts b/lib/logging/logger_factory.ts index 9830acd48..2aee1b535 100644 --- a/lib/logging/logger_factory.ts +++ b/lib/logging/logger_factory.ts @@ -15,6 +15,10 @@ */ import { ConsoleLogHandler, LogHandler, LogLevel, OptimizelyLogger } from './logger'; import { errorResolver, infoResolver, MessageResolver } from '../message/message_resolver'; +import { Maybe } from '../utils/type'; + +export const INVALID_LOG_HANDLER = 'Invalid log handler'; +export const INVALID_LEVEL_PRESET = 'Invalid level preset'; type LevelPreset = { level: LogLevel, @@ -67,6 +71,9 @@ export const ERROR: OpaqueLevelPreset = { }; export const extractLevelPreset = (preset: OpaqueLevelPreset): LevelPreset => { + if (!preset || typeof preset !== 'object' || !preset[levelPresetSymbol]) { + throw new Error(INVALID_LEVEL_PRESET); + } return preset[levelPresetSymbol] as LevelPreset; } @@ -81,8 +88,18 @@ export type LoggerConfig = { logHandler?: LogHandler, }; +const validateLogHandler = (logHandler: any) => { + if (typeof logHandler !== 'object' || typeof logHandler.log !== 'function') { + throw new Error(INVALID_LOG_HANDLER); + } +} + export const createLogger = (config: LoggerConfig): OpaqueLogger => { const { level, infoResolver, errorResolver } = extractLevelPreset(config.level); + + if (config.logHandler) { + validateLogHandler(config.logHandler); + } const loggerName = 'Optimizely'; @@ -103,7 +120,10 @@ export const wrapLogger = (logger: OptimizelyLogger): OpaqueLogger => { }; }; -export const extractLogger = (logger: OpaqueLogger): OptimizelyLogger => { - return logger[loggerSymbol] as OptimizelyLogger; -}; +export const extractLogger = (logger: Maybe<OpaqueLogger>): Maybe<OptimizelyLogger> => { + if (!logger || typeof logger !== 'object') { + return undefined; + } + return logger[loggerSymbol] as Maybe<OptimizelyLogger>; +}; diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index b47e718bf..720baa377 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -23,14 +23,11 @@ export const INVALID_DATAFILE = 'Datafile is invalid - property %s: %s'; export const INVALID_DATAFILE_MALFORMED = 'Datafile is invalid because it is malformed.'; export const INVALID_CONFIG = 'Provided Optimizely config is in an invalid format.'; export const INVALID_JSON = 'JSON object is not valid.'; -export const INVALID_ERROR_HANDLER = 'Provided "errorHandler" is in an invalid format.'; -export const INVALID_EVENT_DISPATCHER = 'Provided "eventDispatcher" is in an invalid format.'; export const INVALID_EVENT_TAGS = 'Provided event tags are in an invalid format.'; export const INVALID_EXPERIMENT_KEY = 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; export const INVALID_EXPERIMENT_ID = 'Experiment ID %s is not in datafile.'; export const INVALID_GROUP_ID = 'Group ID %s is not in datafile.'; -export const INVALID_LOGGER = 'Provided "logger" is in an invalid format.'; export const INVALID_USER_ID = 'Provided user ID is in an invalid format.'; export const INVALID_USER_PROFILE_SERVICE = 'Provided user profile service instance is in an invalid format: %s.'; export const MISSING_INTEGRATION_KEY = diff --git a/lib/odp/odp_manager_factory.spec.ts b/lib/odp/odp_manager_factory.spec.ts index 9815f3085..b80689b79 100644 --- a/lib/odp/odp_manager_factory.spec.ts +++ b/lib/odp/odp_manager_factory.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { getMockSyncCache } from '../tests/mock/mock_cache'; vi.mock('./odp_manager', () => { return { @@ -90,6 +91,45 @@ describe('getOdpManager', () => { MockExponentialBackoff.mockClear(); }); + it('should throw and error if provided segment cache is invalid', () => { + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: 'abc' as any + })).toThrow('Invalid cache'); + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: {} as any, + })).toThrow('Invalid cache method save, Invalid cache method lookup, Invalid cache method reset'); + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: { save: 'abc', lookup: 'abc', reset: 'abc' } as any, + })).toThrow('Invalid cache method save, Invalid cache method lookup, Invalid cache method reset'); + + const noop = () => {}; + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: { save: noop, lookup: 'abc', reset: 'abc' } as any, + })).toThrow('Invalid cache method lookup, Invalid cache method reset'); + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: { save: noop, lookup: noop, reset: 'abc' } as any, + })).toThrow('Invalid cache method reset'); + }); + describe('segment manager', () => { it('should create a default segment manager with default api manager using the passed eventRequestHandler', () => { const segmentRequestHandler = getMockRequestHandler(); @@ -109,7 +149,7 @@ describe('getOdpManager', () => { }); it('should create a default segment manager with the provided segment cache', () => { - const segmentsCache = {} as any; + const segmentsCache = getMockSyncCache<string[]>(); const odpManager = getOdpManager({ segmentsCache, diff --git a/lib/odp/odp_manager_factory.ts b/lib/odp/odp_manager_factory.ts index 91504d5e6..9fd689964 100644 --- a/lib/odp/odp_manager_factory.ts +++ b/lib/odp/odp_manager_factory.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import { RequestHandler } from "../shared_types"; import { Cache } from "../utils/cache/cache"; import { InMemoryLruCache } from "../utils/cache/in_memory_lru_cache"; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import { Maybe } from "../utils/type"; import { DefaultOdpEventApiManager, EventRequestGenerator } from "./event_manager/odp_event_api_manager"; import { DefaultOdpEventManager, OdpEventManager } from "./event_manager/odp_event_manager"; import { DefaultOdpManager, OdpManager } from "./odp_manager"; @@ -34,6 +35,9 @@ export const DEFAULT_EVENT_MAX_RETRIES = 5; export const DEFAULT_EVENT_MIN_BACKOFF = 1000; export const DEFAULT_EVENT_MAX_BACKOFF = 32_000; +export const INVALID_CACHE = 'Invalid cache'; +export const INVALID_CACHE_METHOD = 'Invalid cache method %s'; + const odpManagerSymbol: unique symbol = Symbol(); export type OpaqueOdpManager = { @@ -60,11 +64,32 @@ export type OdpManagerFactoryOptions = Omit<OdpManagerOptions, 'segmentsApiTime eventMaxBackoff?: number; } +const validateCache = (cache: any) => { + const errors = []; + if (!cache || typeof cache !== 'object') { + throw new Error(INVALID_CACHE); + } + + for (const method of ['save', 'lookup', 'reset']) { + if (typeof cache[method] !== 'function') { + errors.push(INVALID_CACHE_METHOD.replace('%s', method)); + } + } + + if (errors.length > 0) { + throw new Error(errors.join(', ')); + } +} + const getDefaultSegmentsCache = (cacheSize?: number, cacheTimeout?: number) => { return new InMemoryLruCache<string[]>(cacheSize || DEFAULT_CACHE_SIZE, cacheTimeout || DEFAULT_CACHE_TIMEOUT); } const getDefaultSegmentManager = (options: OdpManagerFactoryOptions) => { + if (options.segmentsCache) { + validateCache(options.segmentsCache); + } + return new DefaultOdpSegmentManager( options.segmentsCache || getDefaultSegmentsCache(options.segmentsCacheSize, options.segmentsCacheTimeout), new DefaultOdpSegmentApiManager(options.segmentRequestHandler), @@ -104,6 +129,10 @@ export const getOpaqueOdpManager = (options: OdpManagerFactoryOptions): OpaqueOd }; }; -export const extractOdpManager = (manager: OpaqueOdpManager): OdpManager => { - return manager[odpManagerSymbol] as OdpManager; +export const extractOdpManager = (manager: Maybe<OpaqueOdpManager>): Maybe<OdpManager> => { + if (!manager || typeof manager !== 'object') { + return undefined; + } + + return manager[odpManagerSymbol] as Maybe<OdpManager>; } diff --git a/lib/odp/odp_manager_factory.universal.ts b/lib/odp/odp_manager_factory.universal.ts index 9ad2bc250..6bf509611 100644 --- a/lib/odp/odp_manager_factory.universal.ts +++ b/lib/odp/odp_manager_factory.universal.ts @@ -15,6 +15,7 @@ */ import { RequestHandler } from '../utils/http_request_handler/http'; +import { validateRequestHandler } from '../utils/http_request_handler/request_handler_validator'; import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; @@ -27,6 +28,7 @@ export type UniversalOdpManagerOptions = OdpManagerOptions & { }; export const createOdpManager = (options: UniversalOdpManagerOptions): OpaqueOdpManager => { + validateRequestHandler(options.requestHandler); return getOpaqueOdpManager({ ...options, segmentRequestHandler: options.requestHandler, diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index dfe708de4..165fae41b 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import Optimizely from '.'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; -import { createNotificationCenter } from '../notification_center'; import testData from '../tests/test_data'; -import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; -import { LoggerFacade } from '../logging/logger'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; import { createProjectConfig } from '../project_config/project_config'; import { getMockLogger } from '../tests/mock/mock_logger'; import { createOdpManager } from '../odp/odp_manager_factory.node'; @@ -30,7 +28,6 @@ import { getDecisionTestDatafile } from '../tests/decision_test_datafile'; import { DECISION_SOURCES } from '../utils/enums'; import OptimizelyUserContext from '../optimizely_user_context'; import { newErrorDecision } from '../optimizely_decision'; -import { EventDispatcher } from '../shared_types'; import { ImpressionEvent } from '../event_processor/event_builder/user_event'; describe('Optimizely', () => { @@ -53,7 +50,7 @@ describe('Optimizely', () => { vi.spyOn(projectConfigManager, 'makeDisposable'); vi.spyOn(eventProcessor, 'makeDisposable'); - vi.spyOn(odpManager, 'makeDisposable'); + vi.spyOn(odpManager!, 'makeDisposable'); new Optimizely({ clientEngine: 'node-sdk', @@ -68,7 +65,7 @@ describe('Optimizely', () => { expect(projectConfigManager.makeDisposable).toHaveBeenCalled(); expect(eventProcessor.makeDisposable).toHaveBeenCalled(); - expect(odpManager.makeDisposable).toHaveBeenCalled(); + expect(odpManager!.makeDisposable).toHaveBeenCalled(); }); it('should set child logger to respective services', () => { @@ -81,7 +78,7 @@ describe('Optimizely', () => { vi.spyOn(projectConfigManager, 'setLogger'); vi.spyOn(eventProcessor, 'setLogger'); - vi.spyOn(odpManager, 'setLogger'); + vi.spyOn(odpManager!, 'setLogger'); const logger = getMockLogger(); const configChildLogger = getMockLogger(); @@ -104,7 +101,7 @@ describe('Optimizely', () => { expect(projectConfigManager.setLogger).toHaveBeenCalledWith(configChildLogger); expect(eventProcessor.setLogger).toHaveBeenCalledWith(eventProcessorChildLogger); - expect(odpManager.setLogger).toHaveBeenCalledWith(odpManagerChildLogger); + expect(odpManager!.setLogger).toHaveBeenCalledWith(odpManagerChildLogger); }); describe('decideAsync', () => { diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index a7107c479..20debfe31 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2024, Optimizely + * Copyright 2016-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import * as decisionService from '../core/decision_service'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; import * as projectConfig from '../project_config/project_config'; import testData from '../tests/test_data'; -import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; import { createNotificationCenter } from '../notification_center'; import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; diff --git a/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js index 56457a67c..9f3597187 100644 --- a/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2020-2024, Optimizely + * Copyright 2020-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; -import { getForwardingEventProcessor } from '../event_processor/forwarding_event_processor'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; import { FORCED_DECISION_NULL_RULE_KEY } from './index' import { diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 763c235d0..89c60593c 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -15,7 +15,7 @@ */ import { RequestHandler } from "../utils/http_request_handler/http"; -import { Transformer } from "../utils/type"; +import { Maybe, Transformer } from "../utils/type"; import { DatafileManagerConfig } from "./datafile_manager"; import { ProjectConfigManagerImpl, ProjectConfigManager } from "./project_config_manager"; import { PollingDatafileManager } from "./polling_datafile_manager"; @@ -26,6 +26,8 @@ import { MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './co import { LogLevel } from '../logging/logger' import { Store } from "../utils/cache/store"; +export const INVALID_CONFIG_MANAGER = "Invalid config manager"; + const configManagerSymbol: unique symbol = Symbol(); export type OpaqueConfigManager = { @@ -109,5 +111,14 @@ export const wrapConfigManager = (configManager: ProjectConfigManager): OpaqueCo }; export const extractConfigManager = (opaqueConfigManager: OpaqueConfigManager): ProjectConfigManager => { + if (!opaqueConfigManager || typeof opaqueConfigManager !== 'object') { + throw new Error(INVALID_CONFIG_MANAGER); + } + + const configManager = opaqueConfigManager[configManagerSymbol]; + if (!configManager) { + throw new Error(INVALID_CONFIG_MANAGER); + } + return opaqueConfigManager[configManagerSymbol] as ProjectConfigManager; }; diff --git a/lib/project_config/config_manager_factory.universal.ts b/lib/project_config/config_manager_factory.universal.ts index bcbd4a310..bcc664082 100644 --- a/lib/project_config/config_manager_factory.universal.ts +++ b/lib/project_config/config_manager_factory.universal.ts @@ -15,16 +15,15 @@ */ import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; -import { NodeRequestHandler } from "../utils/http_request_handler/request_handler.node"; -import { ProjectConfigManager } from "./project_config_manager"; -import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './constant'; import { RequestHandler } from "../utils/http_request_handler/http"; +import { validateRequestHandler } from "../utils/http_request_handler/request_handler_validator"; export type UniversalPollingConfigManagerConfig = PollingConfigManagerConfig & { requestHandler: RequestHandler; } export const createPollingProjectConfigManager = (config: UniversalPollingConfigManagerConfig): OpaqueConfigManager => { + validateRequestHandler(config.requestHandler); const defaultConfig = { autoUpdate: true, }; diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index abd0a6967..a05c6d266 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016, 2018-2020, 2022, Optimizely + * Copyright 2016, 2018-2020, 2022, 2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,42 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ObjectWithUnknownProperties } from '../../shared_types'; - import { DATAFILE_VERSIONS, } from '../enums'; import { - INVALID_CONFIG, INVALID_DATAFILE_MALFORMED, INVALID_DATAFILE_VERSION, - INVALID_ERROR_HANDLER, - INVALID_EVENT_DISPATCHER, - INVALID_LOGGER, NO_DATAFILE_SPECIFIED, } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE_VERSIONS.V4]; -/** - * Validates the given config options - * @param {unknown} config - * @param {object} config.errorHandler - * @param {object} config.eventDispatcher - * @param {object} config.logger - * @return {boolean} true if the config options are valid - * @throws If any of the config options are not valid - */ -export const validate = function(config: unknown): boolean { - if (typeof config === 'object' && config !== null) { - const configObj = config as ObjectWithUnknownProperties; - // TODO: add validation - return true; - } - throw new OptimizelyError(INVALID_CONFIG); -} - /** * Validates the datafile * @param {Object|string} datafile @@ -80,10 +56,6 @@ export const validateDatafile = function(datafile: unknown): any { return datafile; }; -/** - * Provides utility methods for validating that the configuration options are valid - */ export default { - validate: validate, validateDatafile: validateDatafile, } diff --git a/lib/utils/http_request_handler/request_handler_validator.ts b/lib/utils/http_request_handler/request_handler_validator.ts new file mode 100644 index 000000000..a9df4cc7c --- /dev/null +++ b/lib/utils/http_request_handler/request_handler_validator.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { RequestHandler } from './http'; + +export const INVALID_REQUEST_HANDLER = 'Invalid request handler'; + +export const validateRequestHandler = (requestHandler: RequestHandler): void => { + if (!requestHandler || typeof requestHandler !== 'object') { + throw new Error(INVALID_REQUEST_HANDLER); + } + + if (typeof requestHandler.makeRequest !== 'function') { + throw new Error(INVALID_REQUEST_HANDLER); + } +} diff --git a/lib/vuid/vuid_manager.spec.ts b/lib/vuid/vuid_manager.spec.ts index 5a4713d68..3cfb2a608 100644 --- a/lib/vuid/vuid_manager.spec.ts +++ b/lib/vuid/vuid_manager.spec.ts @@ -22,7 +22,7 @@ import { getMockAsyncCache } from '../tests/mock/mock_cache'; import { isVuid } from './vuid'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { exhaustMicrotasks } from '../tests/testUtils'; -import { get } from 'http'; + const vuidCacheKey = 'optimizely-vuid'; diff --git a/lib/vuid/vuid_manager_factory.ts b/lib/vuid/vuid_manager_factory.ts index 94c777e26..f7f1b760f 100644 --- a/lib/vuid/vuid_manager_factory.ts +++ b/lib/vuid/vuid_manager_factory.ts @@ -29,7 +29,11 @@ export type OpaqueVuidManager = { [vuidManagerSymbol]: unknown; }; -export const extractVuidManager = (opaqueVuidManager: OpaqueVuidManager): Maybe<VuidManager> => { +export const extractVuidManager = (opaqueVuidManager: Maybe<OpaqueVuidManager>): Maybe<VuidManager> => { + if (!opaqueVuidManager || typeof opaqueVuidManager !== 'object') { + return undefined; + } + return opaqueVuidManager[vuidManagerSymbol] as Maybe<VuidManager>; }; From 3e8f29484b77c6cbab0d42f9e81f6b731e825a9a Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 22 May 2025 18:44:12 +0600 Subject: [PATCH 172/200] add node 24 to ci (#1061) --- .github/workflows/javascript.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 30c0bf66e..c097ff585 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ['18', '20', '22'] + node: ['18', '20', '22', '24'] steps: - uses: actions/checkout@v3 - name: Set up Node ${{ matrix.node }} From e3234c7537e7d82d35a8c2a808ba7e739fab5a62 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 22 May 2025 23:57:46 +0600 Subject: [PATCH 173/200] update datafile validation (#1062) --- lib/utils/config_validator/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index a05c6d266..49c927f49 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -51,6 +51,8 @@ export const validateDatafile = function(datafile: unknown): any { if (SUPPORTED_VERSIONS.indexOf(datafile['version' as keyof unknown]) === -1) { throw new OptimizelyError(INVALID_DATAFILE_VERSION, datafile['version' as keyof unknown]); } + } else { + throw new OptimizelyError(INVALID_DATAFILE_MALFORMED); } return datafile; From 8fa0c9b5f22de9df6eeb1fe18237a75618fdc831 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 29 May 2025 00:46:17 +0600 Subject: [PATCH 174/200] throw error from createUserContext in case of invalid input (#1063) --- lib/optimizely/index.tests.js | 24 +++++++++++++----------- lib/optimizely/index.ts | 14 ++++++++++---- lib/shared_types.ts | 2 +- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 20debfe31..dc9d6f6ed 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -17,7 +17,7 @@ import { assert, expect } from 'chai'; import sinon from 'sinon'; import { sprintf } from '../utils/fns'; import { NOTIFICATION_TYPES } from '../notification_center/type'; -import Optimizely from './'; +import Optimizely, { INVALID_ATTRIBUTES, INVALID_IDENTIFIER } from './'; import OptimizelyUserContext from '../optimizely_user_context'; import { OptimizelyDecideOption } from '../shared_types'; import AudienceEvaluator from '../core/audience_evaluator'; @@ -4379,14 +4379,16 @@ describe('lib/optimizely', function() { assert.deepEqual(userId, user.getUserId()); }); - it('should return null OptimizelyUserContext when input userId is null', function() { - var user = optlyInstance.createUserContext(null); - assert.deepEqual(null, user); + it('should throw error when input userId is null', function() { + assert.throws(() => { + optlyInstance.createUserContext(null); + }, Error, INVALID_IDENTIFIER); }); - it('should return null OptimizelyUserContext when input userId is undefined', function() { - var user = optlyInstance.createUserContext(undefined); - assert.deepEqual(null, user); + it('should throw error when input userId is undefined', function() { + assert.throws(() => { + optlyInstance.createUserContext(undefined); + }, Error, INVALID_IDENTIFIER); }); it('should create multiple instances of OptimizelyUserContext', function() { @@ -4405,11 +4407,11 @@ describe('lib/optimizely', function() { assert.deepEqual(user2.getUserId(), userId2); }); - it('should call the error handler for invalid user ID and return null', function() { + it('should call the error handler for invalid user ID and throw', function() { const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), }); - assert.isNull(optlyInstance.createUserContext(1)); + assert.throws(() => optlyInstance.createUserContext(1), Error, INVALID_IDENTIFIER); sinon.assert.calledOnce(errorNotifier.notify); // var errorMessage = errorHandler.handleError.lastCall.args[0].message; // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); @@ -4418,11 +4420,11 @@ describe('lib/optimizely', function() { // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); - it('should call the error handler for invalid attributes and return null', function() { + it('should call the error handler for invalid attributes and throw', function() { const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), }); - assert.isNull(optlyInstance.createUserContext('user1', 'invalid_attributes')); + assert.throws(() => optlyInstance.createUserContext('user1', 'invalid_attributes'), Error, INVALID_ATTRIBUTES); sinon.assert.calledOnce(errorNotifier.notify); // var errorMessage = errorHandler.handleError.lastCall.args[0].message; // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 0d6c937f8..c84d6a4cb 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -114,6 +114,8 @@ type DecisionReasons = (string | number)[]; export const INSTANCE_CLOSED = 'Instance closed'; export const ONREADY_TIMEOUT = 'onReady timeout expired after %s ms'; +export const INVALID_IDENTIFIER = 'Invalid identifier'; +export const INVALID_ATTRIBUTES = 'Invalid attributes'; /** * options required to create optimizely object @@ -1356,13 +1358,17 @@ export default class Optimizely extends BaseService implements Client { * @param {string} userId (Optional) The user ID to be used for bucketing. * @param {UserAttributes} attributes (Optional) user attributes. * @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or - * null if provided inputs are invalid + * throws if provided inputs are invalid */ - createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { + createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext { const userIdentifier = userId ?? this.vuidManager?.getVuid(); - if (userIdentifier === undefined || !this.validateInputs({ user_id: userIdentifier }, attributes)) { - return null; + if (userIdentifier === undefined || !this.validateInputs({ user_id: userIdentifier })) { + throw new Error(INVALID_IDENTIFIER); + } + + if (!this.validateInputs({}, attributes)) { + throw new Error(INVALID_ATTRIBUTES); } const userContext = new OptimizelyUserContext({ diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 0a1582e4a..93d5d4524 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -293,7 +293,7 @@ export interface OptimizelyVariable { export interface Client { // TODO: In the future, will add a function to allow overriding the VUID. getVuid(): string | undefined; - createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null; + createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext; notificationCenter: NotificationCenter; activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null; track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void; From 49f19a6000e459d65486d8695993f6b52fd35126 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 29 May 2025 17:41:15 +0600 Subject: [PATCH 175/200] validate config manager cache (#1064) --- .../event_processor_factory.spec.ts | 4 +- .../event_processor_factory.ts | 21 +------- .../config_manager_factory.spec.ts | 48 ++++++++++++++++++- lib/project_config/config_manager_factory.ts | 5 ++ lib/utils/cache/store_validator.ts | 36 ++++++++++++++ vitest.config.mts | 2 +- 6 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 lib/utils/cache/store_validator.ts diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts index 31b1b62ed..9aaa97f55 100644 --- a/lib/event_processor/event_processor_factory.spec.ts +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -79,14 +79,14 @@ describe('getBatchEventProcessor', () => { defaultFlushInterval: 10000, defaultBatchSize: 10, eventStore: 'abc' as any, - })).toThrow('Invalid event store'); + })).toThrow('Invalid store'); expect(() => getBatchEventProcessor({ eventDispatcher: getMockEventDispatcher(), defaultFlushInterval: 10000, defaultBatchSize: 10, eventStore: 123 as any, - })).toThrow('Invalid event store'); + })).toThrow('Invalid store'); expect(() => getBatchEventProcessor({ eventDispatcher: getMockEventDispatcher(), diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts index dd50c72f2..393ce436a 100644 --- a/lib/event_processor/event_processor_factory.ts +++ b/lib/event_processor/event_processor_factory.ts @@ -23,15 +23,13 @@ import { ForwardingEventProcessor } from "./forwarding_event_processor"; import { BatchEventProcessor, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, EventWithId, RetryConfig } from "./batch_event_processor"; import { AsyncPrefixStore, Store, SyncPrefixStore } from "../utils/cache/store"; import { Maybe } from "../utils/type"; +import { validateStore } from "../utils/cache/store_validator"; export const INVALID_EVENT_DISPATCHER = 'Invalid event dispatcher'; export const FAILED_EVENT_RETRY_INTERVAL = 20 * 1000; export const EVENT_STORE_PREFIX = 'optly_event:'; -export const INVALID_STORE = 'Invalid event store'; -export const INVALID_STORE_METHOD = 'Invalid store method %s'; - export const getPrefixEventStore = (store: Store<string>): Store<EventWithId> => { if (store.operation === 'async') { return new AsyncPrefixStore<string, EventWithId>( @@ -84,23 +82,6 @@ export const validateEventDispatcher = (eventDispatcher: EventDispatcher): void } } -const validateStore = (store: any) => { - const errors = []; - if (!store || typeof store !== 'object') { - throw new Error(INVALID_STORE); - } - - for (const method of ['set', 'get', 'remove', 'getKeys']) { - if (typeof store[method] !== 'function') { - errors.push(INVALID_STORE_METHOD.replace('%s', method)); - } - } - - if (errors.length > 0) { - throw new Error(errors.join(', ')); - } -} - export const getBatchEventProcessor = ( options: BatchEventProcessorFactoryOptions, EventProcessorConstructor: typeof BatchEventProcessor = BatchEventProcessor diff --git a/lib/project_config/config_manager_factory.spec.ts b/lib/project_config/config_manager_factory.spec.ts index 1ad4dc689..7def4f9a8 100644 --- a/lib/project_config/config_manager_factory.spec.ts +++ b/lib/project_config/config_manager_factory.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,52 @@ describe('getPollingConfigManager', () => { MockExponentialBackoff.mockClear(); }); + it('should throw an error if the passed cache is not valid', () => { + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: 1 as any, + })).toThrow('Invalid store'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: 'abc' as any, + })).toThrow('Invalid store'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: {} as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: 'abc', get: 'abc', remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + const noop = () => {}; + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: noop, get: 'abc', remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: noop, get: noop, remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method remove, Invalid store method getKeys'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: noop, get: noop, remove: noop, getKeys: 'abc' } as any, + })).toThrow('Invalid store method getKeys'); + }); + it('uses a repeater with exponential backoff for the datafileManager', () => { const config = { sdkKey: 'sdkKey', diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 89c60593c..e7d21aeea 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -25,6 +25,7 @@ import { StartupLog } from "../service"; import { MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; import { LogLevel } from '../logging/logger' import { Store } from "../utils/cache/store"; +import { validateStore } from "../utils/cache/store_validator"; export const INVALID_CONFIG_MANAGER = "Invalid config manager"; @@ -63,6 +64,10 @@ export type PollingConfigManagerFactoryOptions = PollingConfigManagerConfig & { export const getPollingConfigManager = ( opt: PollingConfigManagerFactoryOptions ): ProjectConfigManager => { + if (opt.cache) { + validateStore(opt.cache); + } + const updateInterval = opt.updateInterval ?? DEFAULT_UPDATE_INTERVAL; const backoff = new ExponentialBackoff(1000, updateInterval, 500); diff --git a/lib/utils/cache/store_validator.ts b/lib/utils/cache/store_validator.ts new file mode 100644 index 000000000..949bb25c3 --- /dev/null +++ b/lib/utils/cache/store_validator.ts @@ -0,0 +1,36 @@ + +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const INVALID_STORE = 'Invalid store'; +export const INVALID_STORE_METHOD = 'Invalid store method %s'; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const validateStore = (store: any): void => { + const errors = []; + if (!store || typeof store !== 'object') { + throw new Error(INVALID_STORE); + } + + for (const method of ['set', 'get', 'remove', 'getKeys']) { + if (typeof store[method] !== 'function') { + errors.push(INVALID_STORE_METHOD.replace('%s', method)); + } + } + + if (errors.length > 0) { + throw new Error(errors.join(', ')); + } +} diff --git a/vitest.config.mts b/vitest.config.mts index 584eeb60d..1bce36eb0 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,5 +1,5 @@ /** - * Copyright 2024 Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From e46273a0877c2071133ac8d8933d031a8d75446b Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 29 May 2025 21:29:53 +0600 Subject: [PATCH 176/200] serialize event count in store function call (#1065) if multiple events are processed concurrently, all event process request might read the initial store size and write to the store, potentially exceeding the store size limit. Serializing the store size read should fix this. Once the size is loaded in memory, further event process read should just read the in memory value --- lib/event_processor/batch_event_processor.ts | 22 +++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 48ce32927..b573ca6aa 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -74,6 +74,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { private batchSize: number; private eventStore?: Store<EventWithId>; private eventCountInStore: Maybe<number> = undefined; + private eventCountWaitPromise: Promise<unknown> = Promise.resolve(); private maxEventsInStore: number = MAX_EVENTS_IN_STORE; private dispatchRepeater: Repeater; private failedEventRepeater?: Repeater; @@ -264,15 +265,22 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } } - private async findEventCountInStore(): Promise<void> { + private async readEventCountInStore(store: Store<EventWithId>): Promise<void> { + try { + const keys = await store.getKeys(); + this.eventCountInStore = keys.length; + } catch (e) { + this.logger?.error(e); + } + } + + private async findEventCountInStore(): Promise<unknown> { if (this.eventStore && this.eventCountInStore === undefined) { - try { - const keys = await this.eventStore.getKeys(); - this.eventCountInStore = keys.length; - } catch (e) { - this.logger?.error(e); - } + const store = this.eventStore; + this.eventCountWaitPromise = this.eventCountWaitPromise.then(() => this.readEventCountInStore(store)); + return this.eventCountWaitPromise; } + return Promise.resolve(); } private async storeEvent(eventWithId: EventWithId): Promise<void> { From dd1375ee384298a1ba94322805a5c55608c8af3e Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 29 May 2025 22:14:32 +0600 Subject: [PATCH 177/200] [FSSDK-11403] init doc update (#1031) --- MIGRATION.md | 464 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 145 +++++++++++----- 2 files changed, 564 insertions(+), 45 deletions(-) create mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 000000000..d462c7d66 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,464 @@ +# Migrating v5 to v6 + +This guide will help you migrate your implementation from Optimizely JavaScript SDK v5 to v6. The new version introduces several architectural changes that provide more flexibility and control over SDK components. + +## Table of Contents + +1. [Major Changes](#major-changes) +2. [Client Initialization](#client-initialization) +3. [Project Configuration Management](#project-configuration-management) +4. [Event Processing](#event-processing) +5. [ODP Management](#odp-management) +6. [VUID Management](#vuid-management) +7. [Error Handling](#error-handling) +8. [Logging](#logging) +9. [onReady Promise Behavior](#onready-promise-behavior) +10. [Dispose of Client](#dispose-of-client) +11. [Migration Examples](#migration-examples) + +## Major Changes + +In v6, the SDK architecture has been modularized to give you more control over different components: + +- The monolithic `createInstance` call is now split into multiple factory functions +- Core functionality (project configuration, event processing, ODP, VUID, logging, and error handling) is now configured through dedicated components created via factory functions, giving you greater flexibility and control in enabling/disabling certain components and allowing optimizing the bundle size for frontend projects. +- Event dispatcher interface has been updated to use Promises +- onReady Promise behavior has changed + +## Client Initialization + +### v5 (Before) + +```javascript +import { createInstance } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>', + datafile: datafile, // optional + datafileOptions: { + autoUpdate: true, + updateInterval: 300000, // 5 minutes + }, + eventBatchSize: 10, + eventFlushInterval: 1000, + logLevel: LogLevel.DEBUG, + errorHandler: { handleError: (error) => console.error(error) }, + odpOptions: { + disabled: false, + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + } +}); +``` + +### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + createVuidManager, + createLogger, + createErrorNotifier, + DEBUG +} from "@optimizely/optimizely-sdk"; + +// Create a project config manager +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + datafile: datafile, // optional + autoUpdate: true, + updateInterval: 300000, // 5 minutes in milliseconds +}); + +// Create an event processor +const eventProcessor = createBatchEventProcessor({ + batchSize: 10, + flushInterval: 1000, +}); + +// Create an ODP manager +const odpManager = createOdpManager({ + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes +}); + +// Create a VUID manager (optional) +const vuidManager = createVuidManager({ + enableVuid: true +}); + +// Create a logger +const logger = createLogger({ + level: DEBUG +}); + +// Create an error notifier +const errorNotifier = createErrorNotifier({ + handleError: (error) => console.error(error) +}); + +// Create the Optimizely client instance +const optimizely = createInstance({ + projectConfigManager, + eventProcessor, + odpManager, + vuidManager, + logger, + errorNotifier +}); +``` + +In case an invalid config is passed to `createInstance`, it returned `null` in v5. In v6, it will throw an error instead of returning null. + +## Project Configuration Management + +In v6, datafile management must be configured by passing in a `projectConfigManager`. Choose either: + +### Polling Project Config Manager + +For automatic datafile updates: + +```javascript +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + datafile: datafileString, // optional + autoUpdate: true, + updateInterval: 60000, // 1 minute + urlTemplate: 'https://custom-cdn.com/datafiles/%s.json' // optional +}); +``` + +### Static Project Config Manager + +When you want to manage datafile updates manually or want to use a fixed datafile: + +```javascript +const projectConfigManager = createStaticProjectConfigManager({ + datafile: datafileString, +}); +``` + +## Event Processing + +In v5, a batch event processor was enabled by default. In v6, an event processor must be instantiated and passed in +explicitly to `createInstance` via the `eventProcessor` option to enable event processing, otherwise no events will +be dispatched. v6 provides two types of event processors: + +### Batch Event Processor + +Queues events and sends them in batches: + +```javascript +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 10, // optional, default is 10 + flushInterval: 1000, // optional, default 1000 for browser +}); +``` + +### Forwarding Event Processor + +Sends events immediately: + +```javascript +const forwardingEventProcessor = createForwardingEventProcessor(); +``` + +### Custom event dispatcher +In both v5 and v6, custom event dispatchers must implement the `EventDispatcher` interface. In v6, the `EventDispatcher` interface has been updated so that the `dispatchEvent` method returns a Promise instead of calling a callback. + +In v5 (Before): + +```javascript +export type EventDispatcherResponse = { + statusCode: number +} + +export type EventDispatcherCallback = (response: EventDispatcherResponse) => void + +export interface EventDispatcher { + dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void +} +``` + +In v6(After): + +```javascript +export type EventDispatcherResponse = { + statusCode?: number +} + +export interface EventDispatcher { + dispatchEvent(event: LogEvent): Promise<EventDispatcherResponse> +} +``` + +## ODP Management + +In v5, ODP functionality was configured via `odpOptions` and enabled by default. In v6, instantiate an OdpManager and pass to `createInstance` to enable ODP: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>', + odpOptions: { + disabled: false, + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + eventApiTimeout: 1000, + segmentsApiTimeout: 1000, + } +}); +``` + +### v6 (After) + +```javascript +const odpManager = createOdpManager({ + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + eventApiTimeout: 1000, + segmentsApiTimeout: 1000, + eventBatchSize: 5, // Now configurable in browser + eventFlushInterval: 3000, // Now configurable in browser +}); + +const optimizely = createInstance({ + projectConfigManager, + odpManager +}); +``` + +To disable ODP functionality in v6, simply don't provide an ODP Manager to the client instance. + +## VUID Management + +In v6, VUID tracking is disabled by default and must be explicitly enabled by createing a vuidManager with `enableVuid` set to `true` and passing it to `createInstance`: + +```javascript +const vuidManager = createVuidManager({ + enableVuid: true, // Explicitly enable VUID tracking +}); + +const optimizely = createInstance({ + projectConfigManager, + vuidManager +}); +``` + +## Error Handling + +Error handling in v6 uses a new errorNotifier object: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + errorHandler: { + handleError: (error) => { + console.error("Custom error handler", error); + } + } +}); +``` + +### v6 (After) + +```javascript +const errorNotifier = createErrorNotifier({ + handleError: (error) => { + console.error("Custom error handler", error); + } +}); + +const optimizely = createInstance({ + projectConfigManager, + errorNotifier +}); +``` + +## Logging + +Logging in v6 is disabled by defualt, and must be enabled by passing in a logger created via a factory function: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + logLevel: LogLevel.DEBUG +}); +``` + +### v6 (After) + +```javascript +import { createLogger, DEBUG } from "@optimizely/optimizely-sdk"; + +const logger = createLogger({ + level: DEBUG +}); + +const optimizely = createInstance({ + projectConfigManager, + logger +}); +``` + +## onReady Promise Behavior + +The `onReady()` method behavior has changed in v6. In v5, onReady() fulfilled with an object that had two fields: `success` and `reason`. If the instance failed to initialize, `success` would be `false` and `reason` will contain an error message. In v6, if onReady() fulfills, that means the instance is ready to use, the fulfillment value is of unknown type and need not to be inspected. If the promise rejects, that means there was an error during initialization. + +### v5 (Before) + +```javascript +optimizely.onReady().then(({ success, reason }) => { + if (success) { + // optimizely is ready to use + } else { + console.log(`initialization unsuccessful: ${reason}`); + } +}); +``` + +### v6 (After) + +```javascript +optimizely + .onReady() + .then(() => { + // optimizely is ready to use + console.log("Client is ready"); + }) + .catch((err) => { + console.error("Error initializing Optimizely client:", err); + }); +``` + +## Migration Examples + +### Basic Example with SDK Key + +#### v5 (Before) + +```javascript +import { createInstance } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>' +}); + +optimizely.onReady().then(({ success }) => { + if (success) { + // Use the client + } +}); +``` + +#### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager +} from '@optimizely/optimizely-sdk'; + +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>' +}); + +const optimizely = createInstance({ + projectConfigManager +}); + +optimizely + .onReady() + .then(() => { + // Use the client + }) + .catch(err => { + console.error(err); + }); +``` + +### Complete Example with ODP and Event Batching + +#### v5 (Before) + +```javascript +import { createInstance, LogLevel } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>', + datafileOptions: { + autoUpdate: true, + updateInterval: 60000 // 1 minute + }, + eventBatchSize: 3, + eventFlushInterval: 10000, // 10 seconds + logLevel: LogLevel.DEBUG, + odpOptions: { + segmentsCacheSize: 10, + segmentsCacheTimeout: 60000 // 1 minute + } +}); + +optimizely.notificationCenter.addNotificationListener( + enums.NOTIFICATION_TYPES.TRACK, + (payload) => { + console.log("Track event", payload); + } +); +``` + +#### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + createLogger, + DEBUG, + NOTIFICATION_TYPES +} from '@optimizely/optimizely-sdk'; + +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + autoUpdate: true, + updateInterval: 60000 // 1 minute +}); + +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 3, + flushInterval: 10000, // 10 seconds +}); + +const odpManager = createOdpManager({ + segmentsCacheSize: 10, + segmentsCacheTimeout: 60000 // 1 minute +}); + +const logger = createLogger({ + level: DEBUG +}); + +const optimizely = createInstance({ + projectConfigManager, + eventProcessor: batchEventProcessor, + odpManager, + logger +}); + +optimizely.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.TRACK, + (payload) => { + console.log("Track event", payload); + } +); +``` + +For complete implementation examples, refer to the [Optimizely JavaScript SDK documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-browser-sdk-v6). diff --git a/README.md b/README.md index 9e16f6cd7..67ac9e583 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) [![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) -This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). +This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). The SDK now features a modular architecture for greater flexibility and control. If you're upgrading from a previous version, see our [Migration Guide](MIGRATION.md). Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/introduction). @@ -21,6 +21,7 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat > For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-node-sdk). > For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: +> > - [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) > - [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) > - [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) @@ -32,95 +33,146 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat ### Prerequisites Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets modern ES6-compliant JavaScript environments. We officially support: + - Node.js >= 18.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. - Modern Web Browsers, such as Microsoft Edge 84+, Firefox 91+, Safari 13+, and Chrome 102+, Opera 76+ In addition, other environments are likely compatible but are not formally supported including: + - Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. - [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. - Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 - ### Install the SDK Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk): Using `npm`: + ```sh npm install --save @optimizely/optimizely-sdk ``` Using `yarn`: + ```sh yarn add @optimizely/optimizely-sdk ``` Using `pnpm`: + ```sh pnpm add @optimizely/optimizely-sdk ``` Using `deno` (no installation required): + ```javascript -import optimizely from "npm:@optimizely/optimizely-sdk" +import optimizely from 'npm:@optimizely/optimizely-sdk'; ``` -## Use the JavaScript SDK (Browser) -See the [Optimizely Feature Experimentation developer documentation for JavaScript (Browser)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. +## Use the JavaScript SDK -### Initialization (Browser) +See the [Optimizely Feature Experimentation developer documentation for JavaScript](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. -The package has different entry points for different environments. The browser entry point is an ES module, which can be used with an appropriate bundler like **Webpack** or **Rollup**. Additionally, for ease of use during initial evaluations you can include a standalone umd bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): +The SDK uses a modular architecture with dedicated components for project configuration, event processing, and more. The examples below demonstrate the recommended initialization pattern. -```html -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.min.js"></script> - -<!-- You can also use the unminified version if necessary --> -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.js"></script> -``` - -When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. - -As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: +### Initialization with Package Managers (npm, yarn, pnpm) ```javascript -const optimizelyClient = window.optimizelySdk.createInstance({ +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, +} from '@optimizely/optimizely-sdk'; + +// 1. Configure your project config manager +const pollingConfigManager = createPollingProjectConfigManager({ sdkKey: '<YOUR_SDK_KEY>', - // datafile: window.optimizelyDatafile, - // etc. + autoUpdate: true, // Optional: enable automatic updates + updateInterval: 300000, // Optional: update every 5 minutes (in ms) }); -optimizelyClient.onReady().then(({ success, reason }) => { - if (success) { - // Create the Optimizely user context, make decisions, and more here! - } +// 2. Create an event processor for analytics +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 10, // Optional: default batch size + flushInterval: 1000, // Optional: flush interval in ms }); -``` -Regarding `EventDispatcher`s: In Node.js and browser environments, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) modules and by [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Browser_compatibility), respectively. In all other environments, you must supply your own `EventDispatcher`. +// 3. Set up ODP manager for segments and audience targeting +const odpManager = createOdpManager(); -## Use the JavaScript SDK (Node) +// 4. Initialize the Optimizely client with the components +const optimizelyClient = createInstance({ + projectConfigManager: pollingConfigManager, + eventProcessor: batchEventProcessor, + odpManager: odpManager, +}); -See the [Optimizely Feature Experimentation developer documentation for JavaScript (Node)](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-node-sdk) to learn how to set up your first JavaScript project and use the SDK for server-side applications. +optimizelyClient + .onReady() + .then(() => { + console.log('Optimizely client is ready'); + // Your application code using Optimizely goes here + }) + .catch(error => { + console.error('Error initializing Optimizely client:', error); + }); +``` -### Initialization (Node) +### Initialization (Using HTML) -The package has different entry points for different environments. The node entry point is CommonJS module. +The package has different entry points for different environments. The browser entry point is an ES module, which can be used with an appropriate bundler like **Webpack** or **Rollup**. Additionally, for ease of use during initial evaluations you can include a standalone umd bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): -```javascript -const optimizelySdk = require('@optimizely/optimizely-sdk'); +```html +<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.min.js"></script> -const optimizelyClient = optimizelySdk.createInstance({ - sdkKey: '<YOUR_SDK_KEY>', - // datafile: window.optimizelyDatafile, - // etc. -}); +<!-- You can also use the unminified version if necessary --> +<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.js"></script> +``` -optimizelyClient.onReady().then(({ success, reason }) => { - if (success) { - // Create the Optimizely user context, make decisions, and more here! - } -}); +When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. + +As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: + +```html +<script> + // Extract the factory functions from the global SDK + const { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + } = window.optimizelySdk; + + // Initialize components + const pollingConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + autoUpdate: true, + }); + + const batchEventProcessor = createBatchEventProcessor(); + + const odpManager = createOdpManager(); + + // Create the Optimizely client + const optimizelyClient = createInstance({ + projectConfigManager: pollingConfigManager, + eventProcessor: batchEventProcessor, + odpManager: odpManager, + }); + + optimizelyClient + .onReady() + .then(() => { + console.log('Optimizely client is ready'); + // Start using the client here + }) + .catch(error => { + console.error('Error initializing Optimizely client:', error); + }); +</script> ``` Regarding `EventDispatcher`s: In Node.js environment, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) module. @@ -165,9 +217,12 @@ For more information regarding contributing to the Optimizely JavaScript SDK, pl ## Special Notes -### Migrating from 4.x.x +### Migration Guides + +If you're updating your SDK version, please check the appropriate migration guide: -This version represents a major version change and, as such, introduces some breaking changes. Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for more details. +- **Migrating from 5.x to 6.x**: See our [Migration Guide](MIGRATION.md) for detailed instructions on updating to the new modular architecture. +- **Migrating from 4.x to 5.x**: Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for details on these breaking changes. ### Feature Management access @@ -201,4 +256,4 @@ First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manag - Ruby - https://github.com/optimizely/ruby-sdk -- Swift - https://github.com/optimizely/swift-sdk \ No newline at end of file +- Swift - https://github.com/optimizely/swift-sdk From fca3a0e09b92465d38e6e778403c9d1f6b532e16 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 29 May 2025 23:37:32 +0600 Subject: [PATCH 178/200] [FSSDK-10843] prepare release 6.0.0 (#1066) --- CHANGELOG.md | 25 +++++++++++++++++++++++++ MIGRATION.md | 2 +- lib/index.browser.tests.js | 2 +- lib/index.node.tests.js | 22 +++++++++++----------- lib/index.react_native.spec.ts | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 8 files changed, 43 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0903ae80f..4d09e1a77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [6.0.0] - May 29, 2025 + +### Breaking Changes + +- Modularized SDK architecture: The monolithic `createInstance` call has been split into multiple factory functions for greater flexibility and control. +- Core functionalities (project configuration, event processing, ODP, VUID, logging, and error handling) are now configured through dedicated components created via factory functions, giving you greater flexibility and control in enabling/disabling certain components and allowing optimizing the bundle size for frontend projects. +- `onReady` Promise behavior changed: It now resolves only when the SDK is ready and rejects on initialization errors. +- event processing is disabled by default and must be explicitly enabled by passing a `eventProcessor` to the client. +- Event dispatcher interface updated to use Promises instead of callbacks. +- Logging is disabled by default and must be explicitly enabled using a logger created via a factory function. +- VUID tracking is disabled by default and must be explicitly enabled by passing a `vuidManager` to the client instance. +- ODP functionality is no longer enabled by default. You must explicitly pass an `odpManager` to enable it. +- Dropped support for older browser versions and Node.js versions earlier than 18.0.0. + +### New Features +- Added support for async user profile service and async decide methods (see dcoumentation for [User Profile Service](https://docs.developers.optimizely.com/feature-experimentation/docs/implement-a-user-profile-service-for-the-javascript-sdk) and [Decide methods](https://docs.developers.optimizely.com/feature-experimentation/docs/decide-methods-for-the-javascript-sdk)) + +### Migration Guide + +For detailed migration instructions, refer to the [Migration Guide](MIGRATION.md). + +### Documentation + +For more details, see the official documentation: [JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk). + ## [5.3.5] - Jan 29, 2025 ### Bug Fixes diff --git a/MIGRATION.md b/MIGRATION.md index d462c7d66..8a2173ebf 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -21,7 +21,7 @@ This guide will help you migrate your implementation from Optimizely JavaScript In v6, the SDK architecture has been modularized to give you more control over different components: - The monolithic `createInstance` call is now split into multiple factory functions -- Core functionality (project configuration, event processing, ODP, VUID, logging, and error handling) is now configured through dedicated components created via factory functions, giving you greater flexibility and control in enabling/disabling certain components and allowing optimizing the bundle size for frontend projects. +- Core functionalities (project configuration, event processing, ODP, VUID, logging, and error handling) are now configured through dedicated components created via factory functions, giving you greater flexibility and control in enabling/disabling certain components and allowing optimizing the bundle size for frontend projects. - Event dispatcher interface has been updated to use Promises - onReady Promise behavior has changed diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 28b94a9d0..82da1278f 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -152,7 +152,7 @@ describe('javascript-sdk (Browser)', function() { }); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '5.3.4'); + assert.equal(optlyInstance.clientVersion, '6.0.0'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 8279c5110..0c6904778 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -79,17 +79,17 @@ describe('optimizelyFactory', function() { // // sinon.assert.calledOnce(fakeLogger.error); // }); - // it('should create an instance of optimizely', function() { - // var optlyInstance = optimizelyFactory.createInstance({ - // projectConfigManager: getMockProjectConfigManager(), - // errorHandler: fakeErrorHandler, - // eventDispatcher: fakeEventDispatcher, - // logger: fakeLogger, - // }); - - // assert.instanceOf(optlyInstance, Optimizely); - // assert.equal(optlyInstance.clientVersion, '5.3.4'); - // }); + it('should create an instance of optimizely', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + }); + + assert.instanceOf(optlyInstance, Optimizely); + assert.equal(optlyInstance.clientVersion, '6.0.0'); + }); // TODO: user will create and inject an event processor // these tests will be refactored accordingly // describe('event processor configuration', function() { diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index d46311278..b1ce89452 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -92,7 +92,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('5.3.4'); + expect(optlyInstance.clientVersion).toEqual('6.0.0'); }); it('should set the React Native JS client engine and javascript SDK version', () => { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 9d1fea0d3..892bff837 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -41,7 +41,7 @@ export const CONTROL_ATTRIBUTES = { export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '5.3.4'; +export const CLIENT_VERSION = '6.0.0'; /* * Represents the source of a decision for feature management. When a feature diff --git a/package-lock.json b/package-lock.json index a1f3df701..3b07d7c32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.4", + "version": "6.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "5.3.4", + "version": "6.0.0", "license": "Apache-2.0", "dependencies": { "decompress-response": "^7.0.0", diff --git a/package.json b/package.json index 40bc9df11..f5e8a583f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "5.3.4", + "version": "6.0.0", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "main": "./dist/index.node.min.js", "browser": "./dist/index.browser.es.min.js", From bfa677e52ca58279f1e2d53f48853b0b5912c02e Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 29 May 2025 23:57:09 +0600 Subject: [PATCH 179/200] [FSSDK-10843] remove crossbrowser and umd test from prepublish (#1067) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5e8a583f..48d7bc0b9 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "build-browser-umd": "rollup -c --config-umd", "coveralls": "nyc --reporter=lcov npm test", "prepare": "npm run build", - "prepublishOnly": "npm test && npm run test-ci", + "prepublishOnly": "npm test", "postbuild:win": "@powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.min.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.min.d.ts\"", "genmsg": "jiti message_generator ./lib/message/error_message.ts ./lib/message/log_message.ts" }, From 760c72b976006018d0fa0f2ab2939963235ba605 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 30 May 2025 00:10:08 +0600 Subject: [PATCH 180/200] update release workflow to use node 18 (#1068) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba839b17d..f3e710a44 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 registry-url: "https://registry.npmjs.org/" always-auth: "true" env: From 5ee0dd46044321248f0a9215004885cb8f0e5331 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 5 Jun 2025 21:42:13 +0600 Subject: [PATCH 181/200] [FSSDK-10000] update README.md (#1069) - add section about closing the instance properly - add warning about memory leak if not closed - other improvements --- README.md | 65 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 67ac9e583..a0ec36fb0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) [![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) -This repository houses the JavaScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). The SDK now features a modular architecture for greater flexibility and control. If you're upgrading from a previous version, see our [Migration Guide](MIGRATION.md). +This is the official JavaScript and TypeScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). The SDK now features a modular architecture for greater flexibility and control. If you're upgrading from a previous version, see our [Migration Guide](MIGRATION.md). Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/introduction). @@ -16,9 +16,8 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat ## Get Started -> For **Browser** applications, refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk) for detailed instructions on getting started with using the SDK within client-side applications. +> Refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk) for detailed instructions on getting started with using the SDK. -> For **Node.js** applications, refer to the [JavaScript (Node) variant of the developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-node-sdk). > For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: > @@ -28,7 +27,7 @@ Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feat > - [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) > - [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) > -> Note: We recommend using the **Lite** version of the sdk for edge platforms. These starter kits also use the **Lite** variant of the JavaScript SDK which excludes the datafile manager and event processor packages. +> Note: We recommend using the **Lite** entrypoint (for version < 6) / **Universal** entrypoint (for version >=6) of the sdk for edge platforms. These starter kits also use the **Lite** variant of the JavaScript SDK. ### Prerequisites @@ -73,7 +72,7 @@ import optimizely from 'npm:@optimizely/optimizely-sdk'; ## Use the JavaScript SDK -See the [Optimizely Feature Experimentation developer documentation for JavaScript](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-sdk) to learn how to set up your first JavaScript project and use the SDK for client-side applications. +See the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk) to learn how to set up your first JavaScript project using the SDK. The SDK uses a modular architecture with dedicated components for project configuration, event processing, and more. The examples below demonstrate the recommended initialization pattern. @@ -121,15 +120,15 @@ optimizelyClient }); ``` -### Initialization (Using HTML) +### Initialization (Using HTML script tag) The package has different entry points for different environments. The browser entry point is an ES module, which can be used with an appropriate bundler like **Webpack** or **Rollup**. Additionally, for ease of use during initial evaluations you can include a standalone umd bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): ```html -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.min.js"></script> +<script src="https://unpkg.com/@optimizely/optimizely-sdk@6/dist/optimizely.browser.umd.min.js"></script> <!-- You can also use the unminified version if necessary --> -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.js"></script> +<script src="https://unpkg.com/@optimizely/optimizely-sdk@6/dist/optimizely.browser.umd.js"></script> ``` When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. @@ -175,21 +174,49 @@ As `window.optimizelySdk` should be a global variable at this point, you can con </script> ``` -Regarding `EventDispatcher`s: In Node.js environment, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) module. +### Closing the SDK Instance + +Depending on the sdk configuration, the client instance might schedule tasks in the background. If the instance has background tasks scheduled, +then the instance will not be garbage collected even though there are no more references to the instance in the code. (Basically, the background tasks will still hold references to the instance). Therefore, it's important to close it to properly clean up resources. + +```javascript +// Close the Optimizely client when you're done using it +optimizelyClient.close() +``` +Using the following settings will cause background tasks to be scheduled + +- Polling Datafile Manager +- Batch Event Processor with batchSize > 1 +- ODP manager with eventBatchSize > 1 + + + +> ⚠️ **Warning**: Failure to close SDK instances when they're no longer needed may result in memory leaks. This is particularly important for applications that create multiple instances over time. For some environment like SSR applications, it might not be convenient to close each instance, in which case, the `disposable` option of `createInstance` can be used to disable all background tasks on the server side, allowing the instance to be garbage collected. + + +## Special Notes + +### Migration Guides + +If you're updating your SDK version, please check the appropriate migration guide: + +- **Migrating from 5.x or lower to 6.x**: See our [Migration Guide](MIGRATION.md) for detailed instructions on updating to the new modular architecture. +- **Migrating from 4.x or lower to 5.x**: Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for details on these breaking changes. ## SDK Development ### Unit Tests -There is a mix of testing paradigms used within the JavaScript SDK which include Mocha, Chai, Karma, and Jest, indicated by their respective `*.tests.js` and `*.spec.ts` filenames. +There is a mix of testing paradigms used within the JavaScript SDK which include Mocha, Chai, Karma, and Vitest, indicated by their respective `*.tests.js` and `*.spec.ts` filenames. When contributing code to the SDK, aim to keep the percentage of code test coverage at the current level ([![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk)) or above. -To run unit tests on the primary JavaScript SDK package source code, you can take the following steps: +To run unit tests, you can take the following steps: -1. On your command line or terminal, navigate to the `~/javascript-sdk/packages/optimizely-sdk` directory. -2. Ensure that you have run `npm install` to install all project dependencies. -3. Run `npm test` to run all test files. +1. Ensure that you have run `npm install` to install all project dependencies. +2. Run `npm test` to run all test files. +3. Run `npm run test-vitest` to run only tests written using Vitest. +4. Run `npm run test-mocha` to run only tests written using Mocha. 4. (For cross-browser testing) Run `npm run test-xbrowser` to run tests in many browsers via BrowserStack. 5. Resolve any tests that fail before continuing with your contribution. @@ -215,14 +242,6 @@ npm run test-xbrowser For more information regarding contributing to the Optimizely JavaScript SDK, please read [Contributing](CONTRIBUTING.md). -## Special Notes - -### Migration Guides - -If you're updating your SDK version, please check the appropriate migration guide: - -- **Migrating from 5.x to 6.x**: See our [Migration Guide](MIGRATION.md) for detailed instructions on updating to the new modular architecture. -- **Migrating from 4.x to 5.x**: Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for details on these breaking changes. ### Feature Management access @@ -232,7 +251,7 @@ To access the Feature Management configuration in the Optimizely dashboard, plea `@optimizely/optimizely-sdk` is developed and maintained by [Optimizely](https://optimizely.com) and many [contributors](https://github.com/optimizely/javascript-sdk/graphs/contributors). If you're interested in learning more about what Optimizely Feature Experimentation can do for your company you can visit the [official Optimizely Feature Experimentation product page here](https://www.optimizely.com/products/experiment/feature-experimentation/) to learn more. -First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manager/lib`, `packages/datafile-manager/src`, `packages/datafile-manager/__test__`, `packages/event-processor/src`, `packages/event-processor/__tests__`, `packages/logging/src`, `packages/logging/__tests__`, `packages/utils/src`, `packages/utils/__tests__`) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. +First-party code (under `lib/`) is copyright Optimizely, Inc., licensed under Apache 2.0. ### Other Optimizely SDKs From b73e364db148128bf2b9d354db2b987b403dc2a9 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 13 Jun 2025 01:06:55 +0600 Subject: [PATCH 182/200] add warning about unversioned cdn urls (#1070) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a0ec36fb0..08ab9f5ad 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ The package has different entry points for different environments. The browser e <script src="https://unpkg.com/@optimizely/optimizely-sdk@6/dist/optimizely.browser.umd.js"></script> ``` +> ⚠️ **Warning**: Always include a specific version number (such as @6) when using CDN URLs like the `unpkg` example above. If you use a URL without a version, your application may automatically receive breaking changes when a new major version is released, which can lead to unexpected issues. + When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: From 41fed0dc20c4955c55a9a789c76a86e15c714d6c Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 23 Jun 2025 17:25:10 +0600 Subject: [PATCH 183/200] [FSSDK-11132] make entity validation configurable in bucketer (#1071) * [FSSDK-11132] make entity validation configurable in bucketer for cmab, we are using a dummy entityId which is not a real variation id. We need to skip entity validation for cmab experiments. --- lib/core/bucketer/index.spec.ts | 6 ++++++ lib/core/bucketer/index.tests.js | 7 +++++++ lib/core/bucketer/index.ts | 19 +++++++++---------- lib/core/decision_service/index.tests.js | 1 + lib/core/decision_service/index.ts | 5 +++++ lib/shared_types.ts | 1 + 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/core/bucketer/index.spec.ts b/lib/core/bucketer/index.spec.ts index b3aac5158..942295356 100644 --- a/lib/core/bucketer/index.spec.ts +++ b/lib/core/bucketer/index.spec.ts @@ -80,6 +80,7 @@ describe('excluding groups', () => { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: mockLogger, + validateEntity: true, }; vi.spyOn(bucketValueGenerator, 'generateBucketValue') @@ -127,6 +128,7 @@ describe('including groups: random', () => { groupIdMap: configObj.groupIdMap, logger: mockLogger, userId: 'testUser', + validateEntity: true, }; }); @@ -228,6 +230,7 @@ describe('including groups: overlapping', () => { groupIdMap: configObj.groupIdMap, logger: mockLogger, userId: 'testUser', + validateEntity: true, }; }); @@ -280,6 +283,7 @@ describe('bucket value falls into empty traffic allocation ranges', () => { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: mockLogger, + validateEntity: true, }; }); @@ -329,6 +333,7 @@ describe('traffic allocation has invalid variation ids', () => { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: mockLogger, + validateEntity: true, }; }); @@ -359,6 +364,7 @@ describe('testBucketWithBucketingId', () => { variationIdMap: configObj.variationIdMap, experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, + validateEntity: true, }; }); diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index 0bdf62f4a..a1e046088 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -74,6 +74,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; sinon .stub(bucketValueGenerator, 'generateBucketValue') @@ -115,6 +116,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; bucketerStub = sinon.stub(bucketValueGenerator, 'generateBucketValue'); }); @@ -135,6 +137,7 @@ describe('lib/core/bucketer', function () { groupIdMap: configObj.groupIdMap, logger: createdLogger, userId: 'testUser', + validateEntity: true, }; }); @@ -225,6 +228,7 @@ describe('lib/core/bucketer', function () { groupIdMap: configObj.groupIdMap, logger: createdLogger, userId: 'testUser', + validateEntity: true, }; }); @@ -269,6 +273,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; }); @@ -316,6 +321,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; }); @@ -365,6 +371,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; }); diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index 686f49abd..b5a5e58c6 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -138,17 +138,16 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ]); const entityId = _findBucket(bucketValue, bucketerParams.trafficAllocationConfig); - if (entityId !== null) { - if (!bucketerParams.variationIdMap[entityId]) { - if (entityId) { - bucketerParams.logger?.warn(INVALID_VARIATION_ID); - decideReasons.push([INVALID_VARIATION_ID]); - } - return { - result: null, - reasons: decideReasons, - }; + + if (bucketerParams.validateEntity && entityId !== null && !bucketerParams.variationIdMap[entityId]) { + if (entityId) { + bucketerParams.logger?.warn(INVALID_VARIATION_ID); + decideReasons.push([INVALID_VARIATION_ID]); } + return { + result: null, + reasons: decideReasons, + }; } return { diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index cdc4dc7c7..346814857 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -653,6 +653,7 @@ describe('lib/core/decision_service', function() { experimentIdMap: configObj.experimentIdMap, experimentKeyMap: configObj.experimentKeyMap, groupIdMap: configObj.groupIdMap, + validateEntity: true, }; assert.deepEqual(bucketerParams, expectedParams); diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 5d5e57da9..70673d68e 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -594,12 +594,16 @@ export class DecisionService { bucketingId: string, userId: string ): BucketerParams { + let validateEntity = true; + let trafficAllocationConfig: TrafficAllocation[] = getTrafficAllocation(configObj, experiment.id); if (experiment.cmab) { trafficAllocationConfig = [{ entityId: CMAB_DUMMY_ENTITY_ID, endOfRange: experiment.cmab.trafficAllocation }]; + + validateEntity = false; } return { @@ -613,6 +617,7 @@ export class DecisionService { trafficAllocationConfig, userId, variationIdMap: configObj.variationIdMap, + validateEntity, } } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 93d5d4524..0b02adbc6 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -64,6 +64,7 @@ export interface BucketerParams { variationIdMap: { [id: string]: Variation }; logger?: LoggerFacade; bucketingId: string; + validateEntity?: boolean; } export interface DecisionResponse<T> { From 7885261fdf499c7b08577403a58d2e1b744efd1f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 25 Jun 2025 20:00:56 +0600 Subject: [PATCH 184/200] [FSSDK-11638] cleanups in event processing (#1073) ## Summary - remove redundant functions - small update in batch event processor eventStoreInCount maintenance logic --- lib/event_processor/batch_event_processor.ts | 4 + .../event_builder/log_event.spec.ts | 36 +++--- .../event_builder/log_event.ts | 42 ------- .../event_builder/user_event.ts | 104 ++++++++++-------- 4 files changed, 78 insertions(+), 108 deletions(-) diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index b573ca6aa..6ad19eaf8 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -266,6 +266,10 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } private async readEventCountInStore(store: Store<EventWithId>): Promise<void> { + if (this.eventCountInStore !== undefined) { + return; + } + try { const keys = await store.getKeys(); this.eventCountInStore = keys.length; diff --git a/lib/event_processor/event_builder/log_event.spec.ts b/lib/event_processor/event_builder/log_event.spec.ts index 54a9c2acf..3dfa07f08 100644 --- a/lib/event_processor/event_builder/log_event.spec.ts +++ b/lib/event_processor/event_builder/log_event.spec.ts @@ -16,15 +16,13 @@ import { describe, it, expect } from 'vitest'; import { - buildConversionEventV1, - buildImpressionEventV1, makeEventBatch, } from './log_event'; import { ImpressionEvent, ConversionEvent } from './user_event'; -describe('buildImpressionEventV1', () => { - it('should build an ImpressionEventV1 when experiment and variation are defined', () => { +describe('makeEventBatch', () => { + it('should build a batch with single impression event when experiment and variation are defined', () => { const impressionEvent: ImpressionEvent = { type: 'impression', timestamp: 69, @@ -65,7 +63,7 @@ describe('buildImpressionEventV1', () => { enabled: true, } - const result = buildImpressionEventV1(impressionEvent) + const result = makeEventBatch([impressionEvent]) expect(result).toEqual({ client_name: 'node-sdk', client_version: '3.0.0', @@ -123,7 +121,7 @@ describe('buildImpressionEventV1', () => { }) }) - it('should build an ImpressionEventV1 when experiment and variation are not defined', () => { + it('should build a batch with simlge impression event when experiment and variation are not defined', () => { const impressionEvent: ImpressionEvent = { type: 'impression', timestamp: 69, @@ -164,7 +162,7 @@ describe('buildImpressionEventV1', () => { enabled: true, } - const result = buildImpressionEventV1(impressionEvent) + const result = makeEventBatch([impressionEvent]) expect(result).toEqual({ client_name: 'node-sdk', client_version: '3.0.0', @@ -220,11 +218,9 @@ describe('buildImpressionEventV1', () => { }, ], }) - }) -}) + }); -describe('buildConversionEventV1', () => { - it('should build a ConversionEventV1 when tags object is defined', () => { + it('should build a batch with single conversion event when tags object is defined', () => { const conversionEvent: ConversionEvent = { type: 'conversion', timestamp: 69, @@ -260,7 +256,7 @@ describe('buildConversionEventV1', () => { value: 123, } - const result = buildConversionEventV1(conversionEvent) + const result = makeEventBatch([conversionEvent]) expect(result).toEqual({ client_name: 'node-sdk', client_version: '3.0.0', @@ -311,7 +307,7 @@ describe('buildConversionEventV1', () => { }) }) - it('should build a ConversionEventV1 when tags object is undefined', () => { + it('should build a batch with single conversion event when when tags object is undefined', () => { const conversionEvent: ConversionEvent = { type: 'conversion', timestamp: 69, @@ -343,7 +339,7 @@ describe('buildConversionEventV1', () => { value: 123, } - const result = buildConversionEventV1(conversionEvent) + const result = makeEventBatch([conversionEvent]) expect(result).toEqual({ client_name: 'node-sdk', client_version: '3.0.0', @@ -390,7 +386,7 @@ describe('buildConversionEventV1', () => { }) }) - it('should build a ConversionEventV1 when event id is null', () => { + it('should build a batch with single conversion event when event id is null', () => { const conversionEvent: ConversionEvent = { type: 'conversion', timestamp: 69, @@ -422,7 +418,7 @@ describe('buildConversionEventV1', () => { value: 123, } - const result = buildConversionEventV1(conversionEvent) + const result = makeEventBatch([conversionEvent]) expect(result).toEqual({ client_name: 'node-sdk', client_version: '3.0.0', @@ -469,7 +465,7 @@ describe('buildConversionEventV1', () => { }) }) - it('should include revenue and value if they are 0', () => { + it('should include revenue and value for conversion events if they are 0', () => { const conversionEvent: ConversionEvent = { type: 'conversion', timestamp: 69, @@ -505,7 +501,7 @@ describe('buildConversionEventV1', () => { value: 0, } - const result = buildConversionEventV1(conversionEvent) + const result = makeEventBatch([conversionEvent]) expect(result).toEqual({ client_name: 'node-sdk', client_version: '3.0.0', @@ -591,7 +587,7 @@ describe('buildConversionEventV1', () => { value: 123, } - const result = buildConversionEventV1(conversionEvent) + const result = makeEventBatch([conversionEvent]) expect(result).toEqual({ client_name: 'node-sdk', client_version: '3.0.0', @@ -635,9 +631,7 @@ describe('buildConversionEventV1', () => { ], }) }) -}) -describe('makeEventBatch', () => { it('should batch Conversion and Impression events together', () => { const conversionEvent: ConversionEvent = { type: 'conversion', diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index 8e65d6ba1..0afdccaeb 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -214,48 +214,6 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { return visitor } -/** - * Event for usage with v1 logtier - * - * @export - * @interface EventBuilderV1 - */ -export function buildImpressionEventV1(data: ImpressionEvent): EventBatch { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeDecisionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function buildConversionEventV1(data: ConversionEvent): EventBatch { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeConversionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - export function buildLogEvent(events: UserEvent[]): LogEvent { return { url: 'https://logx.optimizely.com/v1/events', diff --git a/lib/event_processor/event_builder/user_event.ts b/lib/event_processor/event_builder/user_event.ts index c6c6c5446..eaa1734d6 100644 --- a/lib/event_processor/event_builder/user_event.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -44,8 +44,11 @@ type EventContext = { botFiltering?: boolean; } -export type BaseUserEvent = { - type: 'impression' | 'conversion'; +type EventType = 'impression' | 'conversion'; + + +export type BaseUserEvent<T extends EventType> = { + type: T; timestamp: number; uuid: string; context: EventContext; @@ -55,9 +58,7 @@ export type BaseUserEvent = { }; }; -export type ImpressionEvent = BaseUserEvent & { - type: 'impression'; - +export type ImpressionEvent = BaseUserEvent<'impression'> & { layer: { id: string | null; } | null; @@ -79,9 +80,7 @@ export type ImpressionEvent = BaseUserEvent & { cmabUuid?: string; }; -export type ConversionEvent = BaseUserEvent & { - type: 'conversion'; - +export type ConversionEvent = BaseUserEvent<'conversion'> & { event: { id: string | null; key: string; @@ -108,6 +107,42 @@ export const areEventContextsEqual = (eventA: UserEvent, eventB: UserEvent): boo ) } +const buildBaseEvent = <T extends EventType>({ + configObj, + userId, + userAttributes, + clientEngine, + clientVersion, + type, +}: { + configObj: ProjectConfig; + userId: string; + userAttributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + type: T; +}): BaseUserEvent<T> => { + return { + type, + timestamp: fns.currentTimestamp(), + uuid: fns.uuid(), + context: { + accountId: configObj.accountId, + projectId: configObj.projectId, + revision: configObj.revision, + clientName: clientEngine, + clientVersion: clientVersion, + anonymizeIP: configObj.anonymizeIP || false, + botFiltering: configObj.botFiltering, + }, + user: { + id: userId, + attributes: buildVisitorAttributes(configObj, userAttributes), + }, + }; + +} + export type ImpressionConfig = { decisionObj: DecisionObj; userId: string; @@ -119,7 +154,6 @@ export type ImpressionConfig = { configObj: ProjectConfig; } - /** * Creates an ImpressionEvent object from decision data * @param {ImpressionConfig} config @@ -146,24 +180,14 @@ export const buildImpressionEvent = function({ const layerId = experimentId !== null ? getLayerId(configObj, experimentId) : null; return { - type: 'impression', - timestamp: fns.currentTimestamp(), - uuid: fns.uuid(), - - user: { - id: userId, - attributes: buildVisitorAttributes(configObj, userAttributes), - }, - - context: { - accountId: configObj.accountId, - projectId: configObj.projectId, - revision: configObj.revision, - clientName: clientEngine, - clientVersion: clientVersion, - anonymizeIP: configObj.anonymizeIP || false, - botFiltering: configObj.botFiltering, - }, + ...buildBaseEvent({ + configObj, + userId, + userAttributes, + clientEngine, + clientVersion, + type: 'impression', + }), layer: { id: layerId, @@ -218,24 +242,14 @@ export const buildConversionEvent = function({ const eventValue = eventTags ? eventTagUtils.getEventValue(eventTags, logger) : null; return { - type: 'conversion', - timestamp: fns.currentTimestamp(), - uuid: fns.uuid(), - - user: { - id: userId, - attributes: buildVisitorAttributes(configObj, userAttributes), - }, - - context: { - accountId: configObj.accountId, - projectId: configObj.projectId, - revision: configObj.revision, - clientName: clientEngine, - clientVersion: clientVersion, - anonymizeIP: configObj.anonymizeIP || false, - botFiltering: configObj.botFiltering, - }, + ...buildBaseEvent({ + configObj, + userId, + userAttributes, + clientEngine, + clientVersion, + type: 'conversion', + }), event: { id: eventId, From a7b62d909e6043f7310e49a774ec2b7eafd36573 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 25 Jun 2025 22:03:47 +0600 Subject: [PATCH 185/200] [FSSDK-11207] add multi region support for logx events (#1072) --- .../event_builder/log_event.spec.ts | 66 ++++++++++++++- .../event_builder/log_event.ts | 11 ++- .../event_builder/user_event.spec.ts | 81 +++++++++++++++++++ .../event_builder/user_event.tests.js | 5 ++ .../event_builder/user_event.ts | 8 ++ lib/project_config/project_config.spec.ts | 23 +++++- lib/project_config/project_config.ts | 7 ++ 7 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 lib/event_processor/event_builder/user_event.spec.ts diff --git a/lib/event_processor/event_builder/log_event.spec.ts b/lib/event_processor/event_builder/log_event.spec.ts index 3dfa07f08..ad3b22b94 100644 --- a/lib/event_processor/event_builder/log_event.spec.ts +++ b/lib/event_processor/event_builder/log_event.spec.ts @@ -17,9 +17,12 @@ import { describe, it, expect } from 'vitest'; import { makeEventBatch, + buildLogEvent, } from './log_event'; -import { ImpressionEvent, ConversionEvent } from './user_event'; +import { ImpressionEvent, ConversionEvent, UserEvent } from './user_event'; +import { Region } from '../../project_config/project_config'; + describe('makeEventBatch', () => { it('should build a batch with single impression event when experiment and variation are defined', () => { @@ -804,3 +807,64 @@ describe('makeEventBatch', () => { }) }) +describe('buildLogEvent', () => { + it('should select the correct URL based on the event context region', () => { + const baseEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true + }, + user: { + id: 'userId', + attributes: [] + }, + layer: { + id: 'layerId' + }, + experiment: { + id: 'expId', + key: 'expKey' + }, + variation: { + id: 'varId', + key: 'varKey' + }, + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true + }; + + // Test for US region + const usEvent = { + ...baseEvent, + context: { + ...baseEvent.context, + region: 'US' as Region + } + }; + + const usResult = buildLogEvent([usEvent]); + expect(usResult.url).toBe('https://logx.optimizely.com/v1/events'); + + // Test for EU region + const euEvent = { + ...baseEvent, + context: { + ...baseEvent.context, + region: 'EU' as Region + } + }; + + const euResult = buildLogEvent([euEvent]); + expect(euResult.url).toBe('https://eu.logx.optimizely.com/v1/events'); + }); +}); diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index 0afdccaeb..d3ec940fa 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -19,10 +19,16 @@ import { CONTROL_ATTRIBUTES } from '../../utils/enums'; import { LogEvent } from '../event_dispatcher/event_dispatcher'; import { EventTags } from '../../shared_types'; +import { Region } from '../../project_config/project_config'; const ACTIVATE_EVENT_KEY = 'campaign_activated' const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' +export const logxEndpoint: Record<Region, string> = { + US: 'https://logx.optimizely.com/v1/events', + EU: 'https://eu.logx.optimizely.com/v1/events', +} + export type EventBatch = { account_id: string project_id: string @@ -215,8 +221,11 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { } export function buildLogEvent(events: UserEvent[]): LogEvent { + const region = events[0]?.context.region || 'US'; + const url = logxEndpoint[region]; + return { - url: 'https://logx.optimizely.com/v1/events', + url, httpVerb: 'POST', params: makeEventBatch(events), } diff --git a/lib/event_processor/event_builder/user_event.spec.ts b/lib/event_processor/event_builder/user_event.spec.ts new file mode 100644 index 000000000..e8cb373b3 --- /dev/null +++ b/lib/event_processor/event_builder/user_event.spec.ts @@ -0,0 +1,81 @@ +import { describe, it, expect, vi } from 'vitest'; +import { buildImpressionEvent, buildConversionEvent } from './user_event'; +import { createProjectConfig, ProjectConfig } from '../../project_config/project_config'; +import { DecisionObj } from '../../core/decision_service'; +import testData from '../../tests/test_data'; + +describe('buildImpressionEvent', () => { + it('should use correct region from projectConfig in event context', () => { + const projectConfig = createProjectConfig( + testData.getTestProjectConfig(), + ) + + const experiment = projectConfig.experiments[0]; + const variation = experiment.variations[0]; + + const decisionObj = { + experiment, + variation, + decisionSource: 'experiment', + } as DecisionObj; + + + const impressionEvent = buildImpressionEvent({ + configObj: projectConfig, + decisionObj, + userId: 'test_user', + flagKey: 'test_flag', + enabled: true, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(impressionEvent.context.region).toBe('US'); + + projectConfig.region = 'EU'; + + const impressionEventEU = buildImpressionEvent({ + configObj: projectConfig, + decisionObj, + userId: 'test_user', + flagKey: 'test_flag', + enabled: true, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(impressionEventEU.context.region).toBe('EU'); + }); +}); + +describe('buildConversionEvent', () => { + it('should use correct region from projectConfig in event context', () => { + const projectConfig = createProjectConfig( + testData.getTestProjectConfig(), + ) + + const conversionEvent = buildConversionEvent({ + configObj: projectConfig, + userId: 'test_user', + eventKey: 'test_event', + eventTags: { revenue: 1000 }, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(conversionEvent.context.region).toBe('US'); + + projectConfig.region = 'EU'; + + const conversionEventEU = buildConversionEvent({ + configObj: projectConfig, + userId: 'test_user', + eventKey: 'test_event', + eventTags: { revenue: 1000 }, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(conversionEventEU.context.region).toBe('EU'); + }); +}); diff --git a/lib/event_processor/event_builder/user_event.tests.js b/lib/event_processor/event_builder/user_event.tests.js index 19964e931..30f271d0e 100644 --- a/lib/event_processor/event_builder/user_event.tests.js +++ b/lib/event_processor/event_builder/user_event.tests.js @@ -26,6 +26,7 @@ describe('user_event', function() { beforeEach(function() { configObj = { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -106,6 +107,7 @@ describe('user_event', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -200,6 +202,7 @@ describe('user_event', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -270,6 +273,7 @@ describe('user_event', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -336,6 +340,7 @@ describe('user_event', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', diff --git a/lib/event_processor/event_builder/user_event.ts b/lib/event_processor/event_builder/user_event.ts index eaa1734d6..5d098e87b 100644 --- a/lib/event_processor/event_builder/user_event.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -23,6 +23,7 @@ import { getEventId, getLayerId, ProjectConfig, + Region, } from '../../project_config/project_config'; import { EventTags, UserAttributes } from '../../shared_types'; @@ -35,6 +36,7 @@ export type VisitorAttribute = { } type EventContext = { + region?: Region; accountId: string; projectId: string; revision: string; @@ -96,7 +98,12 @@ export type UserEvent = ImpressionEvent | ConversionEvent; export const areEventContextsEqual = (eventA: UserEvent, eventB: UserEvent): boolean => { const contextA = eventA.context const contextB = eventB.context + + const regionA: Region = contextA.region || 'US'; + const regionB: Region = contextB.region || 'US'; + return ( + regionA === regionB && contextA.accountId === contextB.accountId && contextA.projectId === contextB.projectId && contextA.clientName === contextB.clientName && @@ -127,6 +134,7 @@ const buildBaseEvent = <T extends EventType>({ timestamp: fns.currentTimestamp(), uuid: fns.uuid(), context: { + region: configObj.region, accountId: configObj.accountId, projectId: configObj.projectId, revision: configObj.revision, diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index 5a0259ee4..b7e7bea31 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -16,7 +16,7 @@ import { describe, it, expect, beforeEach, afterEach, vi, assert, Mock } from 'vitest'; import { sprintf } from '../utils/fns'; import { keyBy } from '../utils/fns'; -import projectConfig, { ProjectConfig } from './project_config'; +import projectConfig, { ProjectConfig, Region } from './project_config'; import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; import testDatafile from '../tests/test_data'; import configValidator from '../utils/config_validator'; @@ -40,6 +40,27 @@ const logger = getMockLogger(); describe('createProjectConfig', () => { let configObj: ProjectConfig; + it('should use US region when no region is specified in datafile', () => { + const datafile = testDatafile.getTestProjectConfig(); + const config = projectConfig.createProjectConfig(datafile); + + expect(config.region).toBe('US'); + }); + + it('should parse region specified in datafile correctly', () => { + const datafileUs = testDatafile.getTestProjectConfig(); + datafileUs.region = 'US'; + + const configUs = projectConfig.createProjectConfig(datafileUs); + expect(configUs.region).toBe('US'); + + const datafileEu = testDatafile.getTestProjectConfig(); + datafileEu.region = 'EU'; + const configEu = projectConfig.createProjectConfig(datafileEu); + + expect(configEu.region).toBe('EU'); + }); + it('should set properties correctly when createProjectConfig is called', () => { const testData: Record<string, any> = testDatafile.getTestProjectConfig(); configObj = projectConfig.createProjectConfig(testData as JSON); diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index e91c4743a..23e79989a 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -70,7 +70,10 @@ interface VariableUsageMap { [id: string]: VariationVariable; } +export type Region = 'US' | 'EU'; + export interface ProjectConfig { + region: Region; revision: string; projectId: string; sdkKey: string; @@ -155,6 +158,10 @@ function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { export const createProjectConfig = function(datafileObj?: JSON, datafileStr: string | null = null): ProjectConfig { const projectConfig = createMutationSafeDatafileCopy(datafileObj); + if (!projectConfig.region) { + projectConfig.region = 'US'; // Default to US region if not specified + } + projectConfig.__datafileStr = datafileStr === null ? JSON.stringify(datafileObj) : datafileStr; /* From 51438cf6a50fcc0deb2692aad22740066c67f6c4 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 2 Jul 2025 01:24:19 +0600 Subject: [PATCH 186/200] [FSSDK-11526] parse holdout from datafile into project config (#1074) * [FSSDK-11526] parse holdout from datafile into project config also add getHoldoutsForFlag() function --- lib/feature_toggle.ts | 34 ++++ lib/project_config/project_config.spec.ts | 198 +++++++++++++++++++++- lib/project_config/project_config.ts | 68 ++++++++ lib/shared_types.ts | 19 ++- 4 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 lib/feature_toggle.ts diff --git a/lib/feature_toggle.ts b/lib/feature_toggle.ts new file mode 100644 index 000000000..22254e4f0 --- /dev/null +++ b/lib/feature_toggle.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +/** + * This module contains feature flags that control the availability of features under development. + * Each flag represents a feature that is not yet ready for production release. These flags + * serve multiple purposes in our development workflow: + * + * When a new feature is in development, it can be safely merged into the main branch + * while remaining disabled in production. This allows continuous integration without + * affecting the stability of production releases. The feature code will be automatically + * removed in production builds through tree-shaking when the flag is disabled. + * + * During development and testing, these flags can be easily mocked to enable/disable + * specific features. Once a feature is complete and ready for release, its corresponding + * flag and all associated checks can be removed from the codebase. + */ + +export const holdout = () => false; diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index b7e7bea31..662488914 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { describe, it, expect, beforeEach, afterEach, vi, assert, Mock } from 'vitest'; +import { describe, it, expect, beforeEach, afterEach, vi, assert, Mock, beforeAll, afterAll } from 'vitest'; import { sprintf } from '../utils/fns'; import { keyBy } from '../utils/fns'; -import projectConfig, { ProjectConfig, Region } from './project_config'; +import projectConfig, { ProjectConfig, getHoldoutsForFlag } from './project_config'; import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; import testDatafile from '../tests/test_data'; import configValidator from '../utils/config_validator'; @@ -32,11 +32,20 @@ import { import { getMockLogger } from '../tests/mock/mock_logger'; import { VariableType } from '../shared_types'; import { OptimizelyError } from '../error/optimizly_error'; +import { mock } from 'node:test'; const buildLogMessageFromArgs = (args: any[]) => sprintf(args[1], ...args.splice(2)); const cloneDeep = (obj: any) => JSON.parse(JSON.stringify(obj)); const logger = getMockLogger(); +const mockHoldoutToggle = vi.hoisted(() => vi.fn()); + +vi.mock('../feature_toggle', () => { + return { + holdout: mockHoldoutToggle, + }; +}); + describe('createProjectConfig', () => { let configObj: ProjectConfig; @@ -298,6 +307,191 @@ describe('createProjectConfig - cmab experiments', () => { }); }); +const getHoldoutDatafile = () => { + const datafile = testDatafile.getTestDecideProjectConfig(); + + // Add holdouts to the datafile + datafile.holdouts = [ + { + id: 'holdout_id_1', + key: 'holdout_1', + status: 'Running', + includeFlags: [], + excludeFlags: [], + audienceIds: ['13389130056'], + audienceConditions: ['or', '13389130056'], + variations: [ + { + id: 'var_id_1', + key: 'holdout_variation_1', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'var_id_1', + endOfRange: 5000 + } + ] + }, + { + id: 'holdout_id_2', + key: 'holdout_2', + status: 'Running', + includeFlags: [], + excludeFlags: ['feature_3'], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'var_id_2', + key: 'holdout_variation_2', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'var_id_2', + endOfRange: 1000 + } + ] + }, + { + id: 'holdout_id_3', + key: 'holdout_3', + status: 'Draft', + includeFlags: ['feature_1'], + excludeFlags: [], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'var_id_2', + key: 'holdout_variation_2', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'var_id_2', + endOfRange: 1000 + } + ] + } + ]; + + return datafile; +} + +describe('createProjectConfig - holdouts, feature toggle is on', () => { + beforeAll(() => { + mockHoldoutToggle.mockReturnValue(true); + }); + + afterAll(() => { + mockHoldoutToggle.mockReset(); + }); + + it('should populate holdouts fields correctly', function() { + const datafile = getHoldoutDatafile(); + + mockHoldoutToggle.mockReturnValue(true); + + const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile))); + + expect(configObj.holdouts).toHaveLength(3); + configObj.holdouts.forEach((holdout, i) => { + expect(holdout).toEqual(expect.objectContaining(datafile.holdouts[i])); + expect(holdout.variationKeyMap).toEqual( + keyBy(datafile.holdouts[i].variations, 'key') + ); + }); + + expect(configObj.holdoutIdMap).toEqual({ + holdout_id_1: configObj.holdouts[0], + holdout_id_2: configObj.holdouts[1], + holdout_id_3: configObj.holdouts[2], + }); + + expect(configObj.globalHoldouts).toHaveLength(2); + expect(configObj.globalHoldouts).toEqual([ + configObj.holdouts[0], // holdout_1 has empty includeFlags + configObj.holdouts[1] // holdout_2 has empty includeFlags + ]); + + expect(configObj.includedHoldouts).toEqual({ + feature_1: [configObj.holdouts[2]], // holdout_3 includes feature_1 + }); + + expect(configObj.excludedHoldouts).toEqual({ + feature_3: [configObj.holdouts[1]] // holdout_2 excludes feature_3 + }); + + expect(configObj.flagHoldoutsMap).toEqual({}); + }); + + it('should handle empty holdouts array', function() { + const datafile = testDatafile.getTestProjectConfig(); + + const configObj = projectConfig.createProjectConfig(datafile); + + expect(configObj.holdouts).toEqual([]); + expect(configObj.holdoutIdMap).toEqual({}); + expect(configObj.globalHoldouts).toEqual([]); + expect(configObj.includedHoldouts).toEqual({}); + expect(configObj.excludedHoldouts).toEqual({}); + expect(configObj.flagHoldoutsMap).toEqual({}); + }); + + it('should handle undefined includeFlags and excludeFlags in holdout', function() { + const datafile = getHoldoutDatafile(); + datafile.holdouts[0].includeFlags = undefined; + datafile.holdouts[0].excludeFlags = undefined; + + const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile))); + + expect(configObj.holdouts).toHaveLength(3); + expect(configObj.holdouts[0].includeFlags).toEqual([]); + expect(configObj.holdouts[0].excludeFlags).toEqual([]); + }); +}); + +describe('getHoldoutsForFlag: feature toggle is on', () => { + beforeAll(() => { + mockHoldoutToggle.mockReturnValue(true); + }); + + afterAll(() => { + mockHoldoutToggle.mockReset(); + }); + + it('should return all applicable holdouts for a flag', () => { + const datafile = getHoldoutDatafile(); + const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile))); + + const feature1Holdouts = getHoldoutsForFlag(configObj, 'feature_1'); + expect(feature1Holdouts).toHaveLength(3); + expect(feature1Holdouts).toEqual([ + configObj.holdouts[0], + configObj.holdouts[1], + configObj.holdouts[2], + ]); + + const feature2Holdouts = getHoldoutsForFlag(configObj, 'feature_2'); + expect(feature2Holdouts).toHaveLength(2); + expect(feature2Holdouts).toEqual([ + configObj.holdouts[0], + configObj.holdouts[1], + ]); + + const feature3Holdouts = getHoldoutsForFlag(configObj, 'feature_3'); + expect(feature3Holdouts).toHaveLength(1); + expect(feature3Holdouts).toEqual([ + configObj.holdouts[0], + ]); + }); +}); + describe('getExperimentId', () => { let testData: Record<string, any>; let configObj: ProjectConfig; diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 23e79989a..7ae95e3e9 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -34,6 +34,7 @@ import { VariationVariable, Integration, FeatureVariableValue, + Holdout, } from '../shared_types'; import { OdpConfig, OdpIntegrationConfig } from '../odp/odp_config'; import { Transformer } from '../utils/type'; @@ -51,6 +52,7 @@ import { } from 'error_message'; import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from 'log_message'; import { OptimizelyError } from '../error/optimizly_error'; +import * as featureToggle from '../feature_toggle'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type @@ -110,6 +112,12 @@ export interface ProjectConfig { integrations: Integration[]; integrationKeyMap?: { [key: string]: Integration }; odpIntegrationConfig: OdpIntegrationConfig; + holdouts: Holdout[]; + holdoutIdMap?: { [id: string]: Holdout }; + globalHoldouts: Holdout[]; + includedHoldouts: { [key: string]: Holdout[]; } + excludedHoldouts: { [key: string]: Holdout[]; } + flagHoldoutsMap: { [key: string]: Holdout[]; } } const EXPERIMENT_RUNNING_STATUS = 'Running'; @@ -335,9 +343,69 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.flagVariationsMap[flagKey] = variations; }); + parseHoldoutsConfig(projectConfig); + return projectConfig; }; +const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => { + if (!featureToggle.holdout()) { + return; + } + + projectConfig.holdouts = projectConfig.holdouts || []; + projectConfig.holdoutIdMap = keyBy(projectConfig.holdouts, 'id'); + projectConfig.globalHoldouts = []; + projectConfig.includedHoldouts = {}; + projectConfig.excludedHoldouts = {}; + projectConfig.flagHoldoutsMap = {}; + + projectConfig.holdouts.forEach((holdout) => { + if (!holdout.includeFlags) { + holdout.includeFlags = []; + } + + if (!holdout.excludeFlags) { + holdout.excludeFlags = []; + } + + holdout.variationKeyMap = keyBy(holdout.variations, 'key'); + if (holdout.includeFlags.length === 0) { + projectConfig.globalHoldouts.push(holdout); + + holdout.excludeFlags.forEach((flagKey) => { + if (!projectConfig.excludedHoldouts[flagKey]) { + projectConfig.excludedHoldouts[flagKey] = []; + } + projectConfig.excludedHoldouts[flagKey].push(holdout); + }); + } else { + holdout.includeFlags.forEach((flagKey) => { + if (!projectConfig.includedHoldouts[flagKey]) { + projectConfig.includedHoldouts[flagKey] = []; + } + projectConfig.includedHoldouts[flagKey].push(holdout); + }); + } + }); +} + +export const getHoldoutsForFlag = (projectConfig: ProjectConfig, flagKey: string): Holdout[] => { + if (projectConfig.flagHoldoutsMap[flagKey]) { + return projectConfig.flagHoldoutsMap[flagKey]; + } + + const flagHoldouts: Holdout[] = [ + ...projectConfig.globalHoldouts.filter((holdout) => { + return !(projectConfig.excludedHoldouts[flagKey] || []).includes(holdout); + }), + ...(projectConfig.includedHoldouts[flagKey] || []), + ]; + + projectConfig.flagHoldoutsMap[flagKey] = flagHoldouts; + return flagHoldouts; +} + /** * Extract all audience segments used in this audience's conditions * @param {Audience} audience Object representing the audience being parsed diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 0b02adbc6..3d3492a2c 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -151,17 +151,20 @@ export interface Variation { variables?: VariationVariable[]; } -export interface Experiment { +export interface ExperimentCore { id: string; key: string; variations: Variation[]; variationKeyMap: { [key: string]: Variation }; - groupId?: string; - layerId: string; - status: string; audienceConditions: Array<string | string[]>; audienceIds: string[]; trafficAllocation: TrafficAllocation[]; +} + +export interface Experiment extends ExperimentCore { + layerId: string; + groupId?: string; + status: string; forcedVariations?: { [key: string]: string }; isRollout?: boolean; cmab?: { @@ -170,6 +173,14 @@ export interface Experiment { }; } +export type HoldoutStatus = 'Draft' | 'Running' | 'Concluded' | 'Archived'; + +export interface Holdout extends ExperimentCore { + status: HoldoutStatus; + includeFlags: string[]; + excludeFlags: string[]; +} + export enum VariableType { BOOLEAN = 'boolean', DOUBLE = 'double', From 27d6a24e30844bfb980109505d3b092a42f284c4 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Wed, 30 Jul 2025 00:02:24 +0600 Subject: [PATCH 187/200] [FSSDK-11528] Holdout support in decision service (#1075) --- lib/core/bucketer/index.ts | 3 +- lib/core/decision_service/index.spec.ts | 423 +++++++++++++++++++++++- lib/core/decision_service/index.ts | 136 +++++++- lib/notification_center/type.ts | 12 +- lib/project_config/project_config.ts | 6 + lib/shared_types.ts | 2 +- lib/utils/enums/index.ts | 1 + 7 files changed, 565 insertions(+), 18 deletions(-) diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index b5a5e58c6..e31c8df4b 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -56,7 +56,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse const decideReasons: DecisionReason[] = []; // Check if user is in a random group; if so, check if user is bucketed into a specific experiment const experiment = bucketerParams.experimentIdMap[bucketerParams.experimentId]; - const groupId = experiment['groupId']; + // Optional chaining skips groupId check for holdout experiments; Holdout experimentId is not in experimentIdMap + const groupId = experiment?.['groupId']; if (groupId) { const group = bucketerParams.groupIdMap[groupId]; if (!group) { diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts index 975653611..e410e5d5f 100644 --- a/lib/core/decision_service/index.spec.ts +++ b/lib/core/decision_service/index.spec.ts @@ -13,25 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { describe, it, expect, vi, MockInstance, beforeEach } from 'vitest'; +import { describe, it, expect, vi, MockInstance, beforeEach, afterEach } from 'vitest'; import { CMAB_DUMMY_ENTITY_ID, CMAB_FETCH_FAILED, DecisionService } from '.'; import { getMockLogger } from '../../tests/mock/mock_logger'; import OptimizelyUserContext from '../../optimizely_user_context'; import { bucket } from '../bucketer'; import { getTestProjectConfig, getTestProjectConfigWithFeatures } from '../../tests/test_data'; import { createProjectConfig, ProjectConfig } from '../../project_config/project_config'; -import { BucketerParams, Experiment, OptimizelyDecideOption, UserAttributes, UserProfile } from '../../shared_types'; +import { BucketerParams, Experiment, Holdout, OptimizelyDecideOption, UserAttributes, UserProfile } from '../../shared_types'; import { CONTROL_ATTRIBUTES, DECISION_SOURCES } from '../../utils/enums'; import { getDecisionTestDatafile } from '../../tests/decision_test_datafile'; import { Value } from '../../utils/promise/operation_value'; - import { USER_HAS_NO_FORCED_VARIATION, VALID_BUCKETING_ID, SAVED_USER_VARIATION, SAVED_VARIATION_NOT_FOUND, } from 'log_message'; - import { EXPERIMENT_NOT_RUNNING, RETURNING_STORED_VARIATION, @@ -48,7 +46,6 @@ import { NO_ROLLOUT_EXISTS, USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, } from '../decision_service/index'; - import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from 'error_message'; type MockLogger = ReturnType<typeof getMockLogger>; @@ -117,11 +114,74 @@ vi.mock('../bucketer', () => ({ bucket: mockBucket, })); +// Mock the feature toggle for holdout tests +const mockHoldoutToggle = vi.hoisted(() => vi.fn()); + +vi.mock('../../feature_toggle', () => ({ + holdout: mockHoldoutToggle, +})); + + const cloneDeep = (d: any) => JSON.parse(JSON.stringify(d)); const testData = getTestProjectConfig(); const testDataWithFeatures = getTestProjectConfigWithFeatures(); +// Utility function to create test datafile with holdout configurations +const getHoldoutTestDatafile = () => { + const datafile = getDecisionTestDatafile(); + + // Add holdouts to the datafile + datafile.holdouts = [ + { + id: 'holdout_running_id', + key: 'holdout_running', + status: 'Running', + includeFlags: [], + excludeFlags: [], + audienceIds: ['4001'], // age_22 audience + audienceConditions: ['or', '4001'], + variations: [ + { + id: 'holdout_variation_running_id', + key: 'holdout_variation_running', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_running_id', + endOfRange: 5000 + } + ] + }, + { + id: "holdout_not_bucketed_id", + key: "holdout_not_bucketed", + status: "Running", + includeFlags: [], + excludeFlags: [], + audienceIds: ['4002'], + audienceConditions: ['or', '4002'], + variations: [ + { + id: 'holdout_not_bucketed_variation_id', + key: 'holdout_not_bucketed_variation', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_not_bucketed_variation_id', + endOfRange: 0, + } + ] + }, + ]; + + return datafile; +}; + const verifyBucketCall = ( call: number, projectConfig: ProjectConfig, @@ -1841,6 +1901,359 @@ describe('DecisionService', () => { expect(userProfileServiceAsync?.lookup).not.toHaveBeenCalled(); expect(userProfileServiceAsync?.save).not.toHaveBeenCalled(); }); + + describe('holdout', () => { + beforeEach(async() => { + mockHoldoutToggle.mockReturnValue(true); + const actualBucketModule = (await vi.importActual('../bucketer')) as { bucket: typeof bucket }; + mockBucket.mockImplementation(actualBucketModule.bucket); + }); + + it('should return holdout variation when user is bucketed into running holdout', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getHoldoutTestDatafile()); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 20, + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_running_id'], + variation: config.variationIdMap['holdout_variation_running_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + + it("should consider global holdout even if local holdout is present", async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + const newEntry = { + id: 'holdout_included_id', + key: 'holdout_included', + status: 'Running', + includeFlags: ['flag_1'], + excludeFlags: [], + audienceIds: ['4002'], // age_40 audience + audienceConditions: ['or', '4002'], + variations: [ + { + id: 'holdout_variation_included_id', + key: 'holdout_variation_included', + variables: [], + }, + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_included_id', + endOfRange: 5000, + }, + ], + }; + datafile.holdouts = [newEntry, ...datafile.holdouts]; + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 20, // satisfies both global holdout (age_22) and included holdout (age_40) audiences + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_running_id'], + variation: config.variationIdMap['holdout_variation_running_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + + it("should consider local holdout if misses global holdout", async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts.push({ + id: 'holdout_included_specific_id', + key: 'holdout_included_specific', + status: 'Running', + includeFlags: ['flag_1'], + excludeFlags: [], + audienceIds: ['4002'], // age_60 audience (age <= 60) + audienceConditions: ['or', '4002'], + variations: [ + { + id: 'holdout_variation_included_specific_id', + key: 'holdout_variation_included_specific', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_included_specific_id', + endOfRange: 5000 + } + ] + }); + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'test_holdout_user', + attributes: { + age: 50, // Does not satisfy global holdout (age_22, age <= 22) but satisfies included holdout (age_60, age <= 60) + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_included_specific_id'], + variation: config.variationIdMap['holdout_variation_included_specific_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + + it('should fallback to experiment when holdout status is not running', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts = datafile.holdouts.map((holdout: Holdout) => { + if(holdout.id === 'holdout_running_id') { + return { + ...holdout, + status: "Draft" + } + } + return holdout; + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_1'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should fallback to experiment when user does not meet holdout audience conditions', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getHoldoutTestDatafile()); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 30, // does not satisfy age_22 audience condition for holdout_running + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should fallback to experiment when user is not bucketed into holdout traffic', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getHoldoutTestDatafile()); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 50, + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should fallback to rollout when no holdout or experiment matches', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + // Modify the datafile to create proper audience conditions for this test + // Make exp_1 and exp_2 use age conditions that won't match our test user + datafile.audiences = datafile.audiences.map((audience: any) => { + if (audience.id === '4001') { // age_22 + return { + ...audience, + conditions: JSON.stringify(["or", {"match": "exact", "name": "age", "type": "custom_attribute", "value": 22}]) + }; + } + if (audience.id === '4002') { // age_60 + return { + ...audience, + conditions: JSON.stringify(["or", {"match": "exact", "name": "age", "type": "custom_attribute", "value": 60}]) + }; + } + return audience; + }); + + // Make exp_2 use a different audience so it won't conflict with delivery_2 + datafile.experiments = datafile.experiments.map((experiment: any) => { + if (experiment.key === 'exp_2') { + return { + ...experiment, + audienceIds: ['4001'], // Change from 4002 to 4001 (age_22) + audienceConditions: ['or', '4001'] + }; + } + return experiment; + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 60, // matches audience 4002 (age_60) used by delivery_2, but not experiments + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + }); + + it('should skip holdouts excluded for specific flags', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts = datafile.holdouts.map((holdout: any) => { + if(holdout.id === 'holdout_running_id') { + return { + ...holdout, + excludeFlags: ['flag_1'] + } + } + return holdout; + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, // satisfies age_22 audience condition (age <= 22) for global holdout, but holdout excludes flag_1 + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_1'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should handle multiple holdouts and use first matching one', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts.push({ + id: 'holdout_second_id', + key: 'holdout_second', + status: 'Running', + includeFlags: [], + excludeFlags: [], + audienceIds: [], // no audience requirements + audienceConditions: [], + variations: [ + { + id: 'holdout_variation_second_id', + key: 'holdout_variation_second', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_second_id', + endOfRange: 5000 + } + ] + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 20, // satisfies audience for holdout_running + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_running_id'], + variation: config.variationIdMap['holdout_variation_running_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + }); }); describe('resolveVariationForFeatureList - sync', () => { diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 70673d68e..057a0e129 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -23,7 +23,6 @@ import { } from '../../utils/enums'; import { getAudiencesById, - getExperimentAudienceConditions, getExperimentFromId, getExperimentFromKey, getFlagVariationByKey, @@ -32,7 +31,7 @@ import { getVariationKeyFromId, isActive, ProjectConfig, - getTrafficAllocation, + getHoldoutsForFlag, } from '../../project_config/project_config'; import { AudienceEvaluator, createAudienceEvaluator } from '../audience_evaluator'; import * as stringValidator from '../../utils/string_value_validator'; @@ -41,10 +40,11 @@ import { DecisionResponse, Experiment, ExperimentBucketMap, + ExperimentCore, FeatureFlag, + Holdout, OptimizelyDecideOption, OptimizelyUserContext, - TrafficAllocation, UserAttributes, UserProfile, UserProfileService, @@ -75,6 +75,7 @@ import { OptimizelyError } from '../../error/optimizly_error'; import { CmabService } from './cmab/cmab_service'; import { Maybe, OpType, OpValue } from '../../utils/type'; import { Value } from '../../utils/promise/operation_value'; +import * as featureToggle from '../../feature_toggle'; export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; export const RETURNING_STORED_VARIATION = @@ -112,9 +113,14 @@ export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = export const CMAB_NOT_SUPPORTED_IN_SYNC = 'CMAB is not supported in sync mode.'; export const CMAB_FETCH_FAILED = 'Failed to fetch CMAB data for experiment %s.'; export const CMAB_FETCHED_VARIATION_INVALID = 'Fetched variation %s for cmab experiment %s is invalid.'; +export const HOLDOUT_NOT_RUNNING = 'Holdout %s is not running.'; +export const USER_MEETS_CONDITIONS_FOR_HOLDOUT = 'User %s meets conditions for holdout %s.'; +export const USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT = 'User %s does not meet conditions for holdout %s.'; +export const USER_BUCKETED_INTO_HOLDOUT_VARIATION = 'User %s is in variation %s of holdout %s.'; +export const USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION = 'User %s is in no holdout variation.'; export interface DecisionObj { - experiment: Experiment | null; + experiment: Experiment | Holdout | null; variation: Variation | null; decisionSource: DecisionSource; cmabUuid?: string; @@ -540,14 +546,15 @@ export class DecisionService { */ private checkIfUserIsInAudience( configObj: ProjectConfig, - experiment: Experiment, + experiment: ExperimentCore, evaluationAttribute: string, user: OptimizelyUserContext, loggingKey?: string | number, ): DecisionResponse<boolean> { const decideReasons: DecisionReason[] = []; - const experimentAudienceConditions = getExperimentAudienceConditions(configObj, experiment.id); + const experimentAudienceConditions = experiment.audienceConditions || experiment.audienceIds; const audiencesById = getAudiencesById(configObj); + this.logger?.debug( EVALUATING_AUDIENCES_COMBINED, evaluationAttribute, @@ -560,7 +567,9 @@ export class DecisionService { loggingKey || experiment.key, JSON.stringify(experimentAudienceConditions), ]); + const result = this.audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, user); + this.logger?.info( AUDIENCE_EVALUATION_RESULT_COMBINED, evaluationAttribute, @@ -590,14 +599,15 @@ export class DecisionService { */ private buildBucketerParams( configObj: ProjectConfig, - experiment: Experiment, + experiment: Experiment | Holdout, bucketingId: string, userId: string ): BucketerParams { let validateEntity = true; - let trafficAllocationConfig: TrafficAllocation[] = getTrafficAllocation(configObj, experiment.id); - if (experiment.cmab) { + let trafficAllocationConfig = experiment.trafficAllocation; + + if ('cmab' in experiment && experiment.cmab) { trafficAllocationConfig = [{ entityId: CMAB_DUMMY_ENTITY_ID, endOfRange: experiment.cmab.trafficAllocation @@ -621,6 +631,99 @@ export class DecisionService { } } + /** + * Determines if a user should be bucketed into a holdout variation. + * @param {ProjectConfig} configObj - The parsed project configuration object. + * @param {Holdout} holdout - The holdout to evaluate. + * @param {OptimizelyUserContext} user - The user context. + * @returns {DecisionResponse<DecisionObj>} - DecisionResponse containing holdout decision and reasons. + */ + private getVariationForHoldout( + configObj: ProjectConfig, + holdout: Holdout, + user: OptimizelyUserContext, + ): DecisionResponse<DecisionObj> { + const userId = user.getUserId(); + const decideReasons: DecisionReason[] = []; + + if (holdout.status !== 'Running') { + const reason: DecisionReason = [HOLDOUT_NOT_RUNNING, holdout.key]; + decideReasons.push(reason); + this.logger?.info(HOLDOUT_NOT_RUNNING, holdout.key); + return { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + + const audienceResult = this.checkIfUserIsInAudience( + configObj, + holdout, + AUDIENCE_EVALUATION_TYPES.EXPERIMENT, + user + ); + decideReasons.push(...audienceResult.reasons); + + if (!audienceResult.result) { + const reason: DecisionReason = [USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT, userId, holdout.key]; + decideReasons.push(reason); + this.logger?.info(USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT, userId, holdout.key); + return { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + + const reason: DecisionReason = [USER_MEETS_CONDITIONS_FOR_HOLDOUT, userId, holdout.key]; + decideReasons.push(reason); + this.logger?.info(USER_MEETS_CONDITIONS_FOR_HOLDOUT, userId, holdout.key); + + const attributes = user.getAttributes(); + const bucketingId = this.getBucketingId(userId, attributes); + const bucketerParams = this.buildBucketerParams(configObj, holdout, bucketingId, userId); + const bucketResult = bucket(bucketerParams); + + decideReasons.push(...bucketResult.reasons); + + if (bucketResult.result) { + const variation = configObj.variationIdMap[bucketResult.result]; + if (variation) { + const bucketReason: DecisionReason = [USER_BUCKETED_INTO_HOLDOUT_VARIATION, userId, holdout.key, variation.key]; + decideReasons.push(bucketReason); + this.logger?.info(USER_BUCKETED_INTO_HOLDOUT_VARIATION, userId, holdout.key, variation.key); + + return { + result: { + experiment: holdout, + variation: variation, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + } + + const noBucketReason: DecisionReason = [USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION, userId]; + decideReasons.push(noBucketReason); + this.logger?.info(USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION, userId); + return { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + /** * Pull the stored variation out of the experimentBucketMap for an experiment/userId * @param {ProjectConfig} configObj The parsed project configuration object @@ -835,6 +938,21 @@ export class DecisionService { reasons: decideReasons, }); } + if (featureToggle.holdout()) { + const holdouts = getHoldoutsForFlag(configObj, feature.key); + + for (const holdout of holdouts) { + const holdoutDecision = this.getVariationForHoldout(configObj, holdout, user); + decideReasons.push(...holdoutDecision.reasons); + + if (holdoutDecision.result.variation) { + return Value.of(op, { + result: holdoutDecision.result, + reasons: decideReasons, + }); + } + } + } return this.getVariationForFeatureExperiment(op, configObj, feature, user, decideOptions, userProfileTracker).then((experimentDecision) => { if (experimentDecision.error || experimentDecision.result.variation !== null) { diff --git a/lib/notification_center/type.ts b/lib/notification_center/type.ts index b433c0121..cbf8467a4 100644 --- a/lib/notification_center/type.ts +++ b/lib/notification_center/type.ts @@ -15,7 +15,15 @@ */ import { LogEvent } from '../event_processor/event_dispatcher/event_dispatcher'; -import { EventTags, Experiment, FeatureVariableValue, UserAttributes, VariableType, Variation } from '../shared_types'; +import { + EventTags, + Experiment, + FeatureVariableValue, + Holdout, + UserAttributes, + VariableType, + Variation, +} from '../shared_types'; import { DecisionSource } from '../utils/enums'; import { Nullable } from '../utils/type'; @@ -25,7 +33,7 @@ export type UserEventListenerPayload = { } export type ActivateListenerPayload = UserEventListenerPayload & { - experiment: Experiment | null; + experiment: Experiment | Holdout | null; variation: Variation | null; logEvent: LogEvent; } diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 7ae95e3e9..1b14e4408 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -370,6 +370,12 @@ const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => { } holdout.variationKeyMap = keyBy(holdout.variations, 'key'); + + projectConfig.variationIdMap = { + ...projectConfig.variationIdMap, + ...keyBy(holdout.variations, 'id'), + }; + if (holdout.includeFlags.length === 0) { projectConfig.globalHoldouts.push(holdout); diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 3d3492a2c..7c2046bf6 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -358,7 +358,7 @@ export interface Client { } export interface ActivateListenerPayload extends ListenerPayload { - experiment: import('./shared_types').Experiment; + experiment: import('./shared_types').ExperimentCore; variation: import('./shared_types').Variation; logEvent: Event; } diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 892bff837..103cdac73 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -53,6 +53,7 @@ export const DECISION_SOURCES = { FEATURE_TEST: 'feature-test', ROLLOUT: 'rollout', EXPERIMENT: 'experiment', + HOLDOUT: 'holdout', } as const; export type DecisionSource = typeof DECISION_SOURCES[keyof typeof DECISION_SOURCES]; From 017e10b1c32a3e4357eb9ce714d849ac85f6b612 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Fri, 1 Aug 2025 23:37:55 +0600 Subject: [PATCH 188/200] [FSSDK-11529] Impression event logic adjustment for holdouts (#1076) --- lib/core/decision_service/index.spec.ts | 22 +- .../event_builder/user_event.ts | 5 +- lib/feature_toggle.ts | 2 +- lib/optimizely/index.spec.ts | 612 ++++++++++++++++++ lib/optimizely/index.ts | 4 +- lib/project_config/project_config.spec.ts | 30 +- lib/project_config/project_config.ts | 38 +- lib/shared_types.ts | 4 +- 8 files changed, 671 insertions(+), 46 deletions(-) diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts index e410e5d5f..9fc9d89a1 100644 --- a/lib/core/decision_service/index.spec.ts +++ b/lib/core/decision_service/index.spec.ts @@ -137,8 +137,8 @@ const getHoldoutTestDatafile = () => { id: 'holdout_running_id', key: 'holdout_running', status: 'Running', - includeFlags: [], - excludeFlags: [], + includedFlags: [], + excludedFlags: [], audienceIds: ['4001'], // age_22 audience audienceConditions: ['or', '4001'], variations: [ @@ -159,8 +159,8 @@ const getHoldoutTestDatafile = () => { id: "holdout_not_bucketed_id", key: "holdout_not_bucketed", status: "Running", - includeFlags: [], - excludeFlags: [], + includedFlags: [], + excludedFlags: [], audienceIds: ['4002'], audienceConditions: ['or', '4002'], variations: [ @@ -1940,8 +1940,8 @@ describe('DecisionService', () => { id: 'holdout_included_id', key: 'holdout_included', status: 'Running', - includeFlags: ['flag_1'], - excludeFlags: [], + includedFlags: ['1001'], + excludedFlags: [], audienceIds: ['4002'], // age_40 audience audienceConditions: ['or', '4002'], variations: [ @@ -1989,8 +1989,8 @@ describe('DecisionService', () => { id: 'holdout_included_specific_id', key: 'holdout_included_specific', status: 'Running', - includeFlags: ['flag_1'], - excludeFlags: [], + includedFlags: ['1001'], + excludedFlags: [], audienceIds: ['4002'], // age_60 audience (age <= 60) audienceConditions: ['or', '4002'], variations: [ @@ -2176,7 +2176,7 @@ describe('DecisionService', () => { if(holdout.id === 'holdout_running_id') { return { ...holdout, - excludeFlags: ['flag_1'] + excludedFlags: ['1001'] } } return holdout; @@ -2212,8 +2212,8 @@ describe('DecisionService', () => { id: 'holdout_second_id', key: 'holdout_second', status: 'Running', - includeFlags: [], - excludeFlags: [], + includedFlags: [], + excludedFlags: [], audienceIds: [], // no audience requirements audienceConditions: [], variations: [ diff --git a/lib/event_processor/event_builder/user_event.ts b/lib/event_processor/event_builder/user_event.ts index 5d098e87b..ae33d65da 100644 --- a/lib/event_processor/event_builder/user_event.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -28,6 +28,7 @@ import { import { EventTags, UserAttributes } from '../../shared_types'; import { LoggerFacade } from '../../logging/logger'; +import { DECISION_SOURCES } from '../../common_exports'; export type VisitorAttribute = { entityId: string @@ -184,8 +185,8 @@ export const buildImpressionEvent = function({ const variationKey = decision.getVariationKey(decisionObj); const variationId = decision.getVariationId(decisionObj); const cmabUuid = decisionObj.cmabUuid; - - const layerId = experimentId !== null ? getLayerId(configObj, experimentId) : null; + const layerId = + experimentId !== null ? (ruleType === DECISION_SOURCES.HOLDOUT ? '' : getLayerId(configObj, experimentId)) : null; return { ...buildBaseEvent({ diff --git a/lib/feature_toggle.ts b/lib/feature_toggle.ts index 22254e4f0..0a647c169 100644 --- a/lib/feature_toggle.ts +++ b/lib/feature_toggle.ts @@ -31,4 +31,4 @@ * flag and all associated checks can be removed from the codebase. */ -export const holdout = () => false; +export const holdout = () => true; diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 165fae41b..0c7e2fc79 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -29,6 +29,35 @@ import { DECISION_SOURCES } from '../utils/enums'; import OptimizelyUserContext from '../optimizely_user_context'; import { newErrorDecision } from '../optimizely_decision'; import { ImpressionEvent } from '../event_processor/event_builder/user_event'; +import { OptimizelyDecideOption } from '../shared_types'; +import { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; + + +const holdoutData = [ + { + id: 'holdout_test_id', + key: 'holdout_test_key', + status: 'Running', + includedFlags: [], + excludedFlags: [], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'holdout_variation_id', + key: 'holdout_variation_key', + variables: [], + featureEnabled: false, + }, + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_id', + endOfRange: 10000, + }, + ], + }, +]; describe('Optimizely', () => { const eventDispatcher = { @@ -212,5 +241,588 @@ describe('Optimizely', () => { const event = processSpy.mock.calls[0][0] as ImpressionEvent; expect(event.cmabUuid).toBe('uuid-cmab'); }); + + + }); + + describe('holdout tests', () => { + let projectConfig: any; + let optimizely: any; + let decisionService: any; + let notificationSpy: any; + let eventProcessor: any; + + beforeEach(() => { + const datafile = getDecisionTestDatafile(); + datafile.holdouts = JSON.parse(JSON.stringify(holdoutData)); // Deep copy to avoid mutations + projectConfig = createProjectConfig(datafile); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + + optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + decisionService = optimizely.decisionService; + + // Setup notification spy + notificationSpy = vi.fn(); + optimizely.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + notificationSpy + ); + }); + + it('should dispatch impression event for holdout decision', async () => { + const processSpy = vi.spyOn(eventProcessor, 'process'); + + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: {}, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.ruleKey).toBe('holdout_test_key'); + expect(decision.flagKey).toBe('flag_1'); + expect(decision.variationKey).toBe('holdout_variation_key'); + expect(decision.enabled).toBe(false); + + expect(eventProcessor.process).toHaveBeenCalledOnce(); + + const event = processSpy.mock.calls[0][0] as ImpressionEvent; + + expect(event.type).toBe('impression'); + expect(event.ruleKey).toBe('holdout_test_key'); + expect(event.ruleType).toBe('holdout'); + expect(event.enabled).toBe(false); + }); + + it('should not dispatch impression event for holdout when DISABLE_DECISION_EVENT is used', async () => { + const processSpy = vi.spyOn(eventProcessor, 'process'); + + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: {}, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); + + expect(decision.ruleKey).toBe('holdout_test_key'); + expect(decision.enabled).toBe(false); + expect(processSpy).not.toHaveBeenCalled(); + }); + + it('should dispatch impression event for holdout decision for isFeatureEnabled', async () => { + const processSpy = vi.spyOn(eventProcessor, 'process'); + + vi.spyOn(decisionService, 'getVariationForFeature').mockReturnValue({ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }); + + const result = optimizely.isFeatureEnabled('flag_1', 'test_user', {}); + + expect(result).toBe(false); + + expect(eventProcessor.process).toHaveBeenCalledOnce(); + const event = processSpy.mock.calls[0][0] as ImpressionEvent; + + expect(event.type).toBe('impression'); + expect(event.ruleKey).toBe('holdout_test_key'); + expect(event.ruleType).toBe('holdout'); + expect(event.enabled).toBe(false); + }); + + it('should send correct decision notification for holdout decision', async () => { + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.flagKey).toBe('flag_1'); + expect(decision.enabled).toBe(false); + expect(decision.variationKey).toBe('holdout_variation_key'); + expect(decision.ruleKey).toBe('holdout_test_key'); + + // Verify decision notification was sent + expect(notificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: false, + variationKey: 'holdout_variation_key', + ruleKey: 'holdout_test_key', + variables: expect.any(Object), + reasons: expect.any(Array), + decisionEventDispatched: true, + }), + }); + }); + + it('should handle holdout with included flags', async () => { + // Modify holdout to include specific flag + const modifiedHoldout = { ...projectConfig.holdouts[0] }; + modifiedHoldout.includedFlags = ['1001']; // flag_1 ID from test datafile + projectConfig.holdouts = [modifiedHoldout]; + + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: modifiedHoldout.variations[0], + experiment: modifiedHoldout, + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(false); + expect(decision.ruleKey).toBe('holdout_test_key'); + expect(decision.variationKey).toBe('holdout_variation_key'); + + // Verify notification shows holdout details + expect(notificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: false, + ruleKey: 'holdout_test_key', + }), + }); + }); + + it('should handle holdout with excluded flags', async () => { + // Modify holdout to exclude specific flag + const modifiedHoldout = { ...projectConfig.holdouts[0] }; + modifiedHoldout.excludedFlags = ['1001']; // flag_1 ID from test datafile + projectConfig.holdouts = [modifiedHoldout]; + + // Mock normal feature test behavior for excluded flag + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.variationIdMap['5003'], + experiment: projectConfig.experimentKeyMap['exp_3'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'BD', age: 80 }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(true); + expect(decision.ruleKey).toBe('exp_3'); + expect(decision.variationKey).toBe('variation_3'); + + // Verify notification shows normal experiment details (not holdout) + expect(notificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'BD', age: 80 }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: true, + ruleKey: 'exp_3', + }), + }); + }); + + it('should handle multiple holdouts with correct priority', async () => { + // Setup multiple holdouts + const holdout1 = { ...projectConfig.holdouts[0] }; + holdout1.excludedFlags = ['1001']; // exclude flag_1 + + const holdout2 = { + id: 'holdout_test_id_2', + key: 'holdout_test_key_2', + status: 'Running', + includedFlags: ['1001'], // include flag_1 + excludedFlags: [], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'holdout_variation_id_2', + key: 'holdout_variation_key_2', + variables: [], + featureEnabled: false, + }, + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_id_2', + endOfRange: 10000, + }, + ], + }; + + projectConfig.holdouts = [holdout1, holdout2]; + + // Mock that holdout2 takes priority due to includedFlags + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: holdout2.variations[0], + experiment: holdout2, + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(false); + expect(decision.ruleKey).toBe('holdout_test_key_2'); + expect(decision.variationKey).toBe('holdout_variation_key_2'); + + // Verify notification shows details of selected holdout + expect(notificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: false, + ruleKey: 'holdout_test_key_2', + }), + }); + }); + + it('should respect sendFlagDecisions setting for holdout events - false', async () => { + // Set sendFlagDecisions to false + projectConfig.sendFlagDecisions = false; + + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + const eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizelyWithConfig = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // Add notification listener + const notificationSpyLocal = vi.fn(); + optimizelyWithConfig.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + notificationSpyLocal + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionServiceLocal = optimizelyWithConfig.decisionService; + vi.spyOn(decisionServiceLocal, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: optimizelyWithConfig, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + await optimizelyWithConfig.decideAsync(user, 'flag_1', []); + + // Impression event should still be dispatched for holdouts even when sendFlagDecisions is false + expect(processSpy).toHaveBeenCalledOnce(); + + // Verify notification shows decisionEventDispatched: true + expect(notificationSpyLocal).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + decisionEventDispatched: true, + }), + }); + }); + + it('should respect sendFlagDecisions setting for holdout events - true', async () => { + // Set sendFlagDecisions to true + projectConfig.sendFlagDecisions = true; + + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + const eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizelyWithConfig = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // Add notification listener + const notificationSpyLocal = vi.fn(); + optimizelyWithConfig.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + notificationSpyLocal + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionServiceLocal = optimizelyWithConfig.decisionService; + vi.spyOn(decisionServiceLocal, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: optimizelyWithConfig, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + await optimizelyWithConfig.decideAsync(user, 'flag_1', []); + + // Impression event should be dispatched for holdouts + expect(processSpy).toHaveBeenCalledOnce(); + + // Verify notification shows decisionEventDispatched: true + expect(notificationSpyLocal).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + decisionEventDispatched: true, + }), + }); + }); + + it('should return correct variable values for holdout decision', async () => { + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(false); + expect(decision.variables).toBeDefined(); + expect(typeof decision.variables).toBe('object'); + + // Verify notification includes variable information + expect(notificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + variables: expect.any(Object), + flagKey: 'flag_1', + enabled: false, + }), + }); + }); + + it('should handle disable decision event option for holdout', async () => { + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + const eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizelyWithEventProcessor = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // Add notification listener + const notificationSpyLocal = vi.fn(); + optimizelyWithEventProcessor.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + notificationSpyLocal + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionServiceLocal = optimizelyWithEventProcessor.decisionService; + vi.spyOn(decisionServiceLocal, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: optimizelyWithEventProcessor, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizelyWithEventProcessor.decideAsync(user, 'flag_1', [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); + + expect(decision.enabled).toBe(false); + expect(decision.ruleKey).toBe('holdout_test_key'); + + // No impression event should be dispatched + expect(processSpy).not.toHaveBeenCalled(); + + // Verify notification shows decisionEventDispatched: false + expect(notificationSpyLocal).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + decisionEventDispatched: false, + }), + }); + }); }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index c84d6a4cb..2f3e3277f 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -652,7 +652,7 @@ export default class Optimizely extends BaseService implements Client { let featureEnabled = decision.getFeatureEnabledFromVariation(decisionObj); - if (decisionSource === DECISION_SOURCES.FEATURE_TEST) { + if (decisionSource === DECISION_SOURCES.FEATURE_TEST || decisionSource === DECISION_SOURCES.HOLDOUT) { sourceInfo = { experimentKey: experimentKey, variationKey: variationKey, @@ -661,6 +661,7 @@ export default class Optimizely extends BaseService implements Client { if ( decisionSource === DECISION_SOURCES.FEATURE_TEST || + decisionSource === DECISION_SOURCES.HOLDOUT || (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)) ) { this.sendImpressionEvent(decisionObj, feature.key, userId, featureEnabled, attributes); @@ -1503,6 +1504,7 @@ export default class Optimizely extends BaseService implements Client { if ( !options[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && (decisionSource === DECISION_SOURCES.FEATURE_TEST || + decisionSource === DECISION_SOURCES.HOLDOUT || (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj))) ) { this.sendImpressionEvent(decisionObj, key, userId, flagEnabled, attributes); diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index 662488914..21d18940c 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -316,8 +316,8 @@ const getHoldoutDatafile = () => { id: 'holdout_id_1', key: 'holdout_1', status: 'Running', - includeFlags: [], - excludeFlags: [], + includedFlags: [], + excludedFlags: [], audienceIds: ['13389130056'], audienceConditions: ['or', '13389130056'], variations: [ @@ -338,8 +338,8 @@ const getHoldoutDatafile = () => { id: 'holdout_id_2', key: 'holdout_2', status: 'Running', - includeFlags: [], - excludeFlags: ['feature_3'], + includedFlags: [], + excludedFlags: ['44829230000'], audienceIds: [], audienceConditions: [], variations: [ @@ -360,8 +360,8 @@ const getHoldoutDatafile = () => { id: 'holdout_id_3', key: 'holdout_3', status: 'Draft', - includeFlags: ['feature_1'], - excludeFlags: [], + includedFlags: ['4482920077'], + excludedFlags: [], audienceIds: [], audienceConditions: [], variations: [ @@ -415,16 +415,16 @@ describe('createProjectConfig - holdouts, feature toggle is on', () => { expect(configObj.globalHoldouts).toHaveLength(2); expect(configObj.globalHoldouts).toEqual([ - configObj.holdouts[0], // holdout_1 has empty includeFlags - configObj.holdouts[1] // holdout_2 has empty includeFlags + configObj.holdouts[0], // holdout_1 has empty includedFlags + configObj.holdouts[1] // holdout_2 has empty includedFlags ]); expect(configObj.includedHoldouts).toEqual({ - feature_1: [configObj.holdouts[2]], // holdout_3 includes feature_1 + feature_1: [configObj.holdouts[2]], // holdout_3 includes feature_1 (ID: 4482920077) }); expect(configObj.excludedHoldouts).toEqual({ - feature_3: [configObj.holdouts[1]] // holdout_2 excludes feature_3 + feature_3: [configObj.holdouts[1]] // holdout_2 excludes feature_3 (ID: 44829230000) }); expect(configObj.flagHoldoutsMap).toEqual({}); @@ -443,16 +443,16 @@ describe('createProjectConfig - holdouts, feature toggle is on', () => { expect(configObj.flagHoldoutsMap).toEqual({}); }); - it('should handle undefined includeFlags and excludeFlags in holdout', function() { + it('should handle undefined includedFlags and excludedFlags in holdout', function() { const datafile = getHoldoutDatafile(); - datafile.holdouts[0].includeFlags = undefined; - datafile.holdouts[0].excludeFlags = undefined; + datafile.holdouts[0].includedFlags = undefined; + datafile.holdouts[0].excludedFlags = undefined; const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile))); expect(configObj.holdouts).toHaveLength(3); - expect(configObj.holdouts[0].includeFlags).toEqual([]); - expect(configObj.holdouts[0].excludeFlags).toEqual([]); + expect(configObj.holdouts[0].includedFlags).toEqual([]); + expect(configObj.holdouts[0].excludedFlags).toEqual([]); }); }); diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 1b14e4408..efb50301e 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -360,13 +360,15 @@ const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => { projectConfig.excludedHoldouts = {}; projectConfig.flagHoldoutsMap = {}; + const featureFlagIdMap = keyBy(projectConfig.featureFlags, 'id'); + projectConfig.holdouts.forEach((holdout) => { - if (!holdout.includeFlags) { - holdout.includeFlags = []; + if (!holdout.includedFlags) { + holdout.includedFlags = []; } - if (!holdout.excludeFlags) { - holdout.excludeFlags = []; + if (!holdout.excludedFlags) { + holdout.excludedFlags = []; } holdout.variationKeyMap = keyBy(holdout.variations, 'key'); @@ -376,22 +378,30 @@ const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => { ...keyBy(holdout.variations, 'id'), }; - if (holdout.includeFlags.length === 0) { + if (holdout.includedFlags.length === 0) { projectConfig.globalHoldouts.push(holdout); - holdout.excludeFlags.forEach((flagKey) => { - if (!projectConfig.excludedHoldouts[flagKey]) { - projectConfig.excludedHoldouts[flagKey] = []; + holdout.excludedFlags.forEach((flagId: string) => { + const flag = featureFlagIdMap[flagId]; + if (flag) { + const flagKey = flag.key; + if (!projectConfig.excludedHoldouts[flagKey]) { + projectConfig.excludedHoldouts[flagKey] = []; + } + projectConfig.excludedHoldouts[flagKey].push(holdout); } - projectConfig.excludedHoldouts[flagKey].push(holdout); }); } else { - holdout.includeFlags.forEach((flagKey) => { - if (!projectConfig.includedHoldouts[flagKey]) { - projectConfig.includedHoldouts[flagKey] = []; + holdout.includedFlags.forEach((flagId: string) => { + const flag = featureFlagIdMap[flagId]; + if (flag) { + const flagKey = flag.key; + if (!projectConfig.includedHoldouts[flagKey]) { + projectConfig.includedHoldouts[flagKey] = []; + } + projectConfig.includedHoldouts[flagKey].push(holdout); } - projectConfig.includedHoldouts[flagKey].push(holdout); - }); + }) } }); } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 7c2046bf6..65b9594b2 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -177,8 +177,8 @@ export type HoldoutStatus = 'Draft' | 'Running' | 'Concluded' | 'Archived'; export interface Holdout extends ExperimentCore { status: HoldoutStatus; - includeFlags: string[]; - excludeFlags: string[]; + includedFlags: string[]; + excludedFlags: string[]; } export enum VariableType { From fb5e0127439d1171c67d0b68698867f38c7d2aa4 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 1 Sep 2025 15:41:18 +0600 Subject: [PATCH 189/200] [FSSDK-11787] fix config parsing performance (#1080) Currently, variationIdMap is being populated as follows: ``` projectConfig.variationIdMap = { ...projectConfig.variationIdMap, ...keyBy(experiment.variations, 'id') }; ``` This creates a new objects for each experiment unnecessarily. This causes large slowdown in project config parsing for datafiles containing a large number of experiments containing a large number of variations. This commit makes this more efficient. --- lib/project_config/project_config.ts | 21 +++----- lib/utils/fns/index.spec.ts | 76 +++++++++++++++++++++++++++- lib/utils/fns/index.ts | 26 +++++----- 3 files changed, 93 insertions(+), 30 deletions(-) diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index efb50301e..9a611ff1a 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { find, objectEntries, objectValues, keyBy } from '../utils/fns'; +import { find, objectEntries, objectValues, keyBy, assignBy } from '../utils/fns'; import { FEATURE_VARIABLE_TYPES } from '../utils/enums'; import configValidator from '../utils/config_validator'; @@ -180,10 +180,10 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str audience.conditions = JSON.parse(audience.conditions as string); }); - projectConfig.audiencesById = { - ...keyBy(projectConfig.audiences, 'id'), - ...keyBy(projectConfig.typedAudiences, 'id'), - } + + projectConfig.audiencesById = {}; + assignBy(projectConfig.audiences, 'id', projectConfig.audiencesById); + assignBy(projectConfig.typedAudiences, 'id', projectConfig.audiencesById); projectConfig.attributes = projectConfig.attributes || []; projectConfig.attributeKeyMap = {}; @@ -267,11 +267,7 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str // Creates { <variationKey>: <variation> } map inside of the experiment experiment.variationKeyMap = keyBy(experiment.variations, 'key'); - // Creates { <variationId>: { key: <variationKey>, id: <variationId> } } mapping for quick lookup - projectConfig.variationIdMap = { - ...projectConfig.variationIdMap, - ...keyBy(experiment.variations, 'id') - }; + assignBy(experiment.variations, 'id', projectConfig.variationIdMap); objectValues(experiment.variationKeyMap || {}).forEach(variation => { if (variation.variables) { @@ -373,10 +369,7 @@ const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => { holdout.variationKeyMap = keyBy(holdout.variations, 'key'); - projectConfig.variationIdMap = { - ...projectConfig.variationIdMap, - ...keyBy(holdout.variations, 'id'), - }; + assignBy(holdout.variations, 'id', projectConfig.variationIdMap); if (holdout.includedFlags.length === 0) { projectConfig.globalHoldouts.push(holdout); diff --git a/lib/utils/fns/index.spec.ts b/lib/utils/fns/index.spec.ts index 3d1cd1502..1eec0d746 100644 --- a/lib/utils/fns/index.spec.ts +++ b/lib/utils/fns/index.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '.' +import { groupBy, objectEntries, objectValues, find, sprintf, keyBy, assignBy } from '.' describe('utils', () => { describe('groupBy', () => { @@ -68,7 +68,7 @@ describe('utils', () => { { key: 'baz', firstName: 'james', lastName: 'foxy' }, ] - expect(keyByUtil(input, item => item.key)).toEqual({ + expect(keyBy(input, 'key')).toEqual({ foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, baz: { key: 'baz', firstName: 'james', lastName: 'foxy' }, @@ -76,6 +76,78 @@ describe('utils', () => { }) }) + describe('assignBy', () => { + it('should assign array elements to an object using the specified key', () => { + const input = [ + { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + { key: 'baz', firstName: 'james', lastName: 'foxy' }, + ] + const base = {} + + assignBy(input, 'key', base) + + expect(base).toEqual({ + foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + baz: { key: 'baz', firstName: 'james', lastName: 'foxy' }, + }) + }) + + it('should append to an existing object', () => { + const input = [ + { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + ] + const base: any = { existing: 'value' } + + assignBy(input, 'key', base) + + expect(base).toEqual({ + existing: 'value', + foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + }) + }) + + it('should handle empty array', () => { + const base: any = { existing: 'value' } + + assignBy([], 'key', base) + + expect(base).toEqual({ existing: 'value' }) + }) + + it('should handle null/undefined array', () => { + const base: any = { existing: 'value' } + + assignBy(null as any, 'key', base) + expect(base).toEqual({ existing: 'value' }) + + assignBy(undefined as any, 'key', base) + expect(base).toEqual({ existing: 'value' }) + }) + + it('should override existing values with the same key', () => { + const input = [ + { key: 'foo', firstName: 'jordan', lastName: 'updated' }, + { key: 'bar', firstName: 'james', lastName: 'new' }, + ] + const base: any = { + foo: { key: 'foo', firstName: 'john', lastName: 'original' }, + existing: 'value' + } + + assignBy(input, 'key', base) + + expect(base).toEqual({ + existing: 'value', + foo: { key: 'foo', firstName: 'jordan', lastName: 'updated' }, + bar: { key: 'bar', firstName: 'james', lastName: 'new' }, + }) + }) + }) + describe('sprintf', () => { it('sprintf(msg)', () => { expect(sprintf('this is my message')).toBe('this is my message') diff --git a/lib/utils/fns/index.ts b/lib/utils/fns/index.ts index 7d9506756..5b07b3aad 100644 --- a/lib/utils/fns/index.ts +++ b/lib/utils/fns/index.ts @@ -25,11 +25,19 @@ export function isSafeInteger(number: unknown): boolean { return typeof number == 'number' && Math.abs(number) <= MAX_SAFE_INTEGER_LIMIT; } -export function keyBy<K>(arr: K[], key: string): { [key: string]: K } { +export function keyBy<K>(arr: K[], key: string): Record<string, K> { if (!arr) return {}; - return keyByUtil(arr, function(item) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (item as any)[key]; + + const base: Record<string, K> = {}; + assignBy(arr, key, base); + return base; +} + +export function assignBy<K>(arr: K[], key: string, base: Record<string, K>): void { + if (!arr) return; + + arr.forEach((e) => { + base[(e as any)[key]] = e; }); } @@ -81,15 +89,6 @@ export function find<K>(arr: K[], cond: (arg: K) => boolean): K | undefined { return found; } -export function keyByUtil<K>(arr: K[], keyByFn: (item: K) => string): { [key: string]: K } { - const map: { [key: string]: K } = {}; - arr.forEach(item => { - const key = keyByFn(item); - map[key] = item; - }); - return map; -} - // TODO[OASIS-6649]: Don't use any type // eslint-disable-next-line @typescript-eslint/no-explicit-any export function sprintf(format: string, ...args: any[]): string { @@ -129,6 +128,5 @@ export default { objectValues, objectEntries, find, - keyByUtil, sprintf, }; From 1fe421ebebe81b584c4d145366a54f12318b3bcb Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 8 Sep 2025 07:16:02 +0600 Subject: [PATCH 190/200] [FSSDK-11823] make Activate listener payload type backwards compatible (#1081) The current type of activate listener payload is not backwards compatible with the type in v6.0.0. This PR makes it compatible. --- lib/feature_toggle.ts | 4 ++- lib/notification_center/type.ts | 4 ++- lib/optimizely/index.spec.ts | 63 ++++++++++++++++++++++++++++----- lib/optimizely/index.tests.js | 3 ++ lib/optimizely/index.ts | 21 ++++++++--- lib/shared_types.ts | 9 +++++ 6 files changed, 90 insertions(+), 14 deletions(-) diff --git a/lib/feature_toggle.ts b/lib/feature_toggle.ts index 0a647c169..cea8adf67 100644 --- a/lib/feature_toggle.ts +++ b/lib/feature_toggle.ts @@ -31,4 +31,6 @@ * flag and all associated checks can be removed from the codebase. */ -export const holdout = () => true; +export const holdout = () => true as const; + +export type IfActive<T extends () => boolean, Y, N = unknown> = ReturnType<T> extends true ? Y : N; diff --git a/lib/notification_center/type.ts b/lib/notification_center/type.ts index cbf8467a4..01adc56e5 100644 --- a/lib/notification_center/type.ts +++ b/lib/notification_center/type.ts @@ -26,6 +26,7 @@ import { } from '../shared_types'; import { DecisionSource } from '../utils/enums'; import { Nullable } from '../utils/type'; +import { holdout, IfActive } from '../feature_toggle'; export type UserEventListenerPayload = { userId: string; @@ -33,7 +34,8 @@ export type UserEventListenerPayload = { } export type ActivateListenerPayload = UserEventListenerPayload & { - experiment: Experiment | Holdout | null; + experiment: Experiment | null; + holdout: IfActive<typeof holdout, Holdout | null>; variation: Variation | null; logEvent: LogEvent; } diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 0c7e2fc79..81509fd1e 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -249,7 +249,8 @@ describe('Optimizely', () => { let projectConfig: any; let optimizely: any; let decisionService: any; - let notificationSpy: any; + let flagNotificationSpy: any; + let activateNotificationSpy: any; let eventProcessor: any; beforeEach(() => { @@ -282,10 +283,16 @@ describe('Optimizely', () => { decisionService = optimizely.decisionService; // Setup notification spy - notificationSpy = vi.fn(); + flagNotificationSpy = vi.fn(); optimizely.notificationCenter.addNotificationListener( NOTIFICATION_TYPES.DECISION, - notificationSpy + flagNotificationSpy + ); + + activateNotificationSpy = vi.fn(); + optimizely.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateNotificationSpy ); }); @@ -408,7 +415,7 @@ describe('Optimizely', () => { expect(decision.ruleKey).toBe('holdout_test_key'); // Verify decision notification was sent - expect(notificationSpy).toHaveBeenCalledWith({ + expect(flagNotificationSpy).toHaveBeenCalledWith({ type: DECISION_NOTIFICATION_TYPES.FLAG, userId: 'test_user', attributes: { country: 'US' }, @@ -422,6 +429,14 @@ describe('Optimizely', () => { decisionEventDispatched: true, }), }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: projectConfig.holdouts[0], + userId: 'test_user', + attributes: { country: 'US' }, + variation: projectConfig.holdouts[0].variations[0] + })); }); it('should handle holdout with included flags', async () => { @@ -455,7 +470,7 @@ describe('Optimizely', () => { expect(decision.variationKey).toBe('holdout_variation_key'); // Verify notification shows holdout details - expect(notificationSpy).toHaveBeenCalledWith({ + expect(flagNotificationSpy).toHaveBeenCalledWith({ type: DECISION_NOTIFICATION_TYPES.FLAG, userId: 'test_user', attributes: { country: 'US' }, @@ -465,6 +480,14 @@ describe('Optimizely', () => { ruleKey: 'holdout_test_key', }), }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: modifiedHoldout, + userId: 'test_user', + attributes: { country: 'US' }, + variation: modifiedHoldout.variations[0] + })); }); it('should handle holdout with excluded flags', async () => { @@ -499,7 +522,7 @@ describe('Optimizely', () => { expect(decision.variationKey).toBe('variation_3'); // Verify notification shows normal experiment details (not holdout) - expect(notificationSpy).toHaveBeenCalledWith({ + expect(flagNotificationSpy).toHaveBeenCalledWith({ type: DECISION_NOTIFICATION_TYPES.FLAG, userId: 'test_user', attributes: { country: 'BD', age: 80 }, @@ -509,6 +532,14 @@ describe('Optimizely', () => { ruleKey: 'exp_3', }), }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: projectConfig.experimentKeyMap['exp_3'], + holdout: null, + userId: 'test_user', + attributes: { country: 'BD', age: 80 }, + variation: projectConfig.variationIdMap['5003'] + })); }); it('should handle multiple holdouts with correct priority', async () => { @@ -568,7 +599,7 @@ describe('Optimizely', () => { expect(decision.variationKey).toBe('holdout_variation_key_2'); // Verify notification shows details of selected holdout - expect(notificationSpy).toHaveBeenCalledWith({ + expect(flagNotificationSpy).toHaveBeenCalledWith({ type: DECISION_NOTIFICATION_TYPES.FLAG, userId: 'test_user', attributes: { country: 'US' }, @@ -578,6 +609,14 @@ describe('Optimizely', () => { ruleKey: 'holdout_test_key_2', }), }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: holdout2, + userId: 'test_user', + attributes: { country: 'US' }, + variation: holdout2.variations[0] + })); }); it('should respect sendFlagDecisions setting for holdout events - false', async () => { @@ -744,7 +783,7 @@ describe('Optimizely', () => { expect(typeof decision.variables).toBe('object'); // Verify notification includes variable information - expect(notificationSpy).toHaveBeenCalledWith({ + expect(flagNotificationSpy).toHaveBeenCalledWith({ type: DECISION_NOTIFICATION_TYPES.FLAG, userId: 'test_user', attributes: { country: 'US' }, @@ -754,6 +793,14 @@ describe('Optimizely', () => { enabled: false, }), }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: projectConfig.holdouts[0], + userId: 'test_user', + attributes: { country: 'US' }, + variation: projectConfig.holdouts[0].variations[0] + })); }); it('should handle disable decision event option for holdout', async () => { diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index dc9d6f6ed..d3f350bba 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -67,6 +67,7 @@ import { import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP } from '../core/bucketer'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { holdout } from '../feature_toggle'; var LOG_LEVEL = enums.LOG_LEVEL; var DECISION_SOURCES = enums.DECISION_SOURCES; @@ -2281,6 +2282,7 @@ describe('lib/optimizely', function() { var instanceExperiments = optlyInstance.projectConfigManager.getConfig().experiments; var expectedArgument = { experiment: instanceExperiments[0], + holdout: null, userId: 'testUser', attributes: undefined, variation: instanceExperiments[0].variations[1], @@ -2351,6 +2353,7 @@ describe('lib/optimizely', function() { var instanceExperiments = optlyInstance.projectConfigManager.getConfig().experiments; var expectedArgument = { experiment: instanceExperiments[0], + holdout: null, userId: 'testUser', attributes: attributes, variation: instanceExperiments[0].variations[1], diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 2f3e3277f..f6e2b4f35 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -37,6 +37,7 @@ import { OptimizelyDecision, Client, UserProfileServiceAsync, + isHoldout, } from '../shared_types'; import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; @@ -62,7 +63,7 @@ import { import { Fn, Maybe, OpType } from '../utils/type'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; -import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; +import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES, ActivateListenerPayload } from '../notification_center/type'; import { FEATURE_NOT_IN_DATAFILE, INVALID_INPUT_FORMAT, @@ -382,13 +383,25 @@ export default class Optimizely extends BaseService implements Client { this.eventProcessor.process(impressionEvent); const logEvent = buildLogEvent([impressionEvent]); - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, { - experiment: decisionObj.experiment, + + const activateNotificationPayload: ActivateListenerPayload = { + experiment: null, + holdout: null, userId: userId, attributes: attributes, variation: decisionObj.variation, logEvent, - }); + }; + + if (decisionObj.experiment) { + if (isHoldout(decisionObj.experiment)) { + activateNotificationPayload.holdout = decisionObj.experiment; + } else { + activateNotificationPayload.experiment = decisionObj.experiment; + } + } + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, activateNotificationPayload); } /** diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 65b9594b2..a79a88b03 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -181,6 +181,15 @@ export interface Holdout extends ExperimentCore { excludedFlags: string[]; } +export function isHoldout(obj: Experiment | Holdout): obj is Holdout { + // Holdout has 'status', 'includedFlags', and 'excludedFlags' properties + return ( + (obj as Holdout).status !== undefined && + Array.isArray((obj as Holdout).includedFlags) && + Array.isArray((obj as Holdout).excludedFlags) + ); +} + export enum VariableType { BOOLEAN = 'boolean', DOUBLE = 'double', From b9fb3713202a4c2a2ff318ba4243a74bc1b6e91a Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Mon, 8 Sep 2025 19:21:56 +0600 Subject: [PATCH 191/200] [FSSDK-11823] prepare release 6.1.0 (#1082) --- CHANGELOG.md | 11 +++++++++++ lib/feature_toggle.ts | 2 +- lib/index.browser.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/index.react_native.spec.ts | 2 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 8 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d09e1a77..ee82dd595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [6.1.0] - September 8, 2025 + +### New Features + +- Added multi-region support for logx events ([#1072](https://github.com/optimizely/javascript-sdk/pull/1072)) + +### Performance Improvements + +- Improved performance of variations parsing from datafile ([#1080](https://github.com/optimizely/javascript-sdk/pull/1080)) +- General cleanups and improvements in event processing ([#1073](https://github.com/optimizely/javascript-sdk/pull/1073)) + ## [6.0.0] - May 29, 2025 ### Breaking Changes diff --git a/lib/feature_toggle.ts b/lib/feature_toggle.ts index cea8adf67..54da8afff 100644 --- a/lib/feature_toggle.ts +++ b/lib/feature_toggle.ts @@ -31,6 +31,6 @@ * flag and all associated checks can be removed from the codebase. */ -export const holdout = () => true as const; +export const holdout = () => false as const; export type IfActive<T extends () => boolean, Y, N = unknown> = ReturnType<T> extends true ? Y : N; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 82da1278f..8b2e93e7f 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -152,7 +152,7 @@ describe('javascript-sdk (Browser)', function() { }); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '6.0.0'); + assert.equal(optlyInstance.clientVersion, '6.1.0'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 0c6904778..343312174 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -88,7 +88,7 @@ describe('optimizelyFactory', function() { }); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '6.0.0'); + assert.equal(optlyInstance.clientVersion, '6.1.0'); }); // TODO: user will create and inject an event processor // these tests will be refactored accordingly diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index b1ce89452..3fa5a6357 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -92,7 +92,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('6.0.0'); + expect(optlyInstance.clientVersion).toEqual('6.1.0'); }); it('should set the React Native JS client engine and javascript SDK version', () => { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 103cdac73..99c254e5e 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -41,7 +41,7 @@ export const CONTROL_ATTRIBUTES = { export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '6.0.0'; +export const CLIENT_VERSION = '6.1.0'; /* * Represents the source of a decision for feature management. When a feature diff --git a/package-lock.json b/package-lock.json index 3b07d7c32..4e6e462c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "6.0.0", + "version": "6.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "6.0.0", + "version": "6.1.0", "license": "Apache-2.0", "dependencies": { "decompress-response": "^7.0.0", diff --git a/package.json b/package.json index 48d7bc0b9..9aa609e6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "6.0.0", + "version": "6.1.0", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "main": "./dist/index.node.min.js", "browser": "./dist/index.browser.es.min.js", From f04de07b45d1332eb7069e4ae56f971d12c748d1 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 9 Sep 2025 00:17:16 +0600 Subject: [PATCH 192/200] [FSSDK-11823] ActivateListenerPaylod type fix in shared_types (#1083) Currently, the notification payload types are exported from shared_types. But the current current types used in the notification center has diverged from these types, and these types are not used in the sdk. Therefore, the sdk user should not import/use these types either. But as these has been publicly released, we cannot change them in a incompatible manner either. So, updating these types as generic map to keep them backwards comptabile. Also exporting the actual payload types being used with new names. The user should use these new types instead. --- lib/export_types.ts | 5 +++++ lib/index.universal.ts | 5 +++++ lib/shared_types.ts | 12 ++++-------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/export_types.ts b/lib/export_types.ts index 53d5c876c..ce795dbaa 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -70,6 +70,11 @@ export type { Store } from './utils/cache/store' export type { NotificationType, NotificationPayload, + ActivateListenerPayload as ActivateNotificationPayload, + DecisionListenerPayload as DecisionNotificationPayload, + TrackListenerPayload as TrackNotificationPayload, + LogEventListenerPayload as LogEventNotificationPayload, + OptimizelyConfigUpdateListenerPayload as OptimizelyConfigUpdateNotificationPayload, } from './notification_center/type'; export type { diff --git a/lib/index.universal.ts b/lib/index.universal.ts index 4ef8cc20f..9aaaa8cd3 100644 --- a/lib/index.universal.ts +++ b/lib/index.universal.ts @@ -101,6 +101,11 @@ export type { Cache } from './utils/cache/cache'; export type { NotificationType, NotificationPayload, + ActivateListenerPayload as ActivateNotificationPayload, + DecisionListenerPayload as DecisionNotificationPayload, + TrackListenerPayload as TrackNotificationPayload, + LogEventListenerPayload as LogEventNotificationPayload, + OptimizelyConfigUpdateListenerPayload as OptimizelyConfigUpdateNotificationPayload, } from './notification_center/type'; export type { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index a79a88b03..45e66413b 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -366,16 +366,12 @@ export interface Client { isOdpIntegrated(): boolean; } -export interface ActivateListenerPayload extends ListenerPayload { - experiment: import('./shared_types').ExperimentCore; - variation: import('./shared_types').Variation; - logEvent: Event; +export interface ActivateListenerPayload { + [key: string]: any; } -export interface TrackListenerPayload extends ListenerPayload { - eventKey: string; - eventTags: EventTags; - logEvent: Event; +export interface TrackListenerPayload { + [key: string]: any; } /** From ccd6adb9fa255aa730f106374819a83c92ffb6e5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 7 Oct 2025 00:57:52 +0600 Subject: [PATCH 193/200] [FSSDK-11898] serialize concurrent cmab service calls (#1086) The cmab service caches the results of a cmab prediction retrieve from the server and returns it for subsequent call. This ensures a consistent value is returned for getDecision() within the cache ttl. However, when there is no cached value, if there is concurrent calls to gertDecision() for same userId and ruleId combination, all of these will cause a call to the server and may potentially return different values. The solution is to run concurrent calls for same userId and ruleId combinations one after another. To achieve this, we put each (userId, ruleId) combination in one of the predefined bucktes by hashing the (userId, ruleId) combination and serialize all calls for that particular hash % (num_buckets). --- .../cmab/cmab_service.spec.ts | 91 ++++++++++- .../decision_service/cmab/cmab_service.ts | 24 +++ lib/utils/executor/serial_runner.spec.ts | 143 ++++++++++++++++++ lib/utils/executor/serial_runner.ts | 36 +++++ 4 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 lib/utils/executor/serial_runner.spec.ts create mode 100644 lib/utils/executor/serial_runner.ts diff --git a/lib/core/decision_service/cmab/cmab_service.spec.ts b/lib/core/decision_service/cmab/cmab_service.spec.ts index dce84f6e1..38ee205e4 100644 --- a/lib/core/decision_service/cmab/cmab_service.spec.ts +++ b/lib/core/decision_service/cmab/cmab_service.spec.ts @@ -1,4 +1,20 @@ -import { describe, it, expect, vi, Mocked, Mock, MockInstance, beforeEach, afterEach } from 'vitest'; +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; import { DefaultCmabService } from './cmab_service'; import { getMockSyncCache } from '../../../tests/mock/mock_cache'; @@ -6,6 +22,8 @@ import { ProjectConfig } from '../../../project_config/project_config'; import { OptimizelyDecideOption, UserAttributes } from '../../../shared_types'; import OptimizelyUserContext from '../../../optimizely_user_context'; import { validate as uuidValidate } from 'uuid'; +import { resolvablePromise } from '../../../utils/promise/resolvablePromise'; +import { exhaustMicrotasks } from '../../../tests/testUtils'; const mockProjectConfig = (): ProjectConfig => ({ experimentIdMap: { @@ -418,4 +436,75 @@ describe('DefaultCmabService', () => { expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); }); + + it('should serialize concurrent calls to getDecision with the same userId and ruleId', async () => { + const nCall = 10; + let currentVar = 123; + const fetchPromises = Array.from({ length: nCall }, () => resolvablePromise()); + + let callCount = 0; + const mockCmabClient = { + fetchDecision: vi.fn().mockImplementation(async () => { + const variation = `${currentVar++}`; + await fetchPromises[callCount++]; + return variation; + }), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', {}); + + const resultPromises = []; + for (let i = 0; i < nCall; i++) { + resultPromises.push(cmabService.getDecision(projectConfig, userContext, '1234', {})); + } + + await exhaustMicrotasks(); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + + for(let i = 0; i < nCall; i++) { + fetchPromises[i].resolve(''); + await exhaustMicrotasks(); + const result = await resultPromises[i]; + expect(result.variationId).toBe('123'); + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + } + }); + + it('should not serialize calls to getDecision with different userId or ruleId', async () => { + let currentVar = 123; + const mockCmabClient = { + fetchDecision: vi.fn().mockImplementation(() => Promise.resolve(`${currentVar++}`)), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext1 = mockUserContext('user123', {}); + const userContext2 = mockUserContext('user456', {}); + + const resultPromises = []; + resultPromises.push(cmabService.getDecision(projectConfig, userContext1, '1234', {})); + resultPromises.push(cmabService.getDecision(projectConfig, userContext1, '5678', {})); + resultPromises.push(cmabService.getDecision(projectConfig, userContext2, '1234', {})); + resultPromises.push(cmabService.getDecision(projectConfig, userContext2, '5678', {})); + + await exhaustMicrotasks(); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(4); + + for(let i = 0; i < resultPromises.length; i++) { + const result = await resultPromises[i]; + expect(result.variationId).toBe(`${123 + i}`); + } + }); }); diff --git a/lib/core/decision_service/cmab/cmab_service.ts b/lib/core/decision_service/cmab/cmab_service.ts index 094e10bbb..cd3ab99ea 100644 --- a/lib/core/decision_service/cmab/cmab_service.ts +++ b/lib/core/decision_service/cmab/cmab_service.ts @@ -23,6 +23,7 @@ import { CmabClient } from "./cmab_client"; import { v4 as uuidV4 } from 'uuid'; import murmurhash from "murmurhash"; import { DecideOptionsMap } from ".."; +import { SerialRunner } from "../../../utils/executor/serial_runner"; export type CmabDecision = { variationId: string, @@ -57,10 +58,15 @@ export type CmabServiceOptions = { cmabClient: CmabClient; } +const SERIALIZER_BUCKETS = 1000; + export class DefaultCmabService implements CmabService { private cmabCache: CacheWithRemove<CmabCacheValue>; private cmabClient: CmabClient; private logger?: LoggerFacade; + private serializers: SerialRunner[] = Array.from( + { length: SERIALIZER_BUCKETS }, () => new SerialRunner() + ); constructor(options: CmabServiceOptions) { this.cmabCache = options.cmabCache; @@ -68,11 +74,29 @@ export class DefaultCmabService implements CmabService { this.logger = options.logger; } + private getSerializerIndex(userId: string, experimentId: string): number { + const key = this.getCacheKey(userId, experimentId); + const hash = murmurhash.v3(key); + return Math.abs(hash) % SERIALIZER_BUCKETS; + } + async getDecision( projectConfig: ProjectConfig, userContext: IOptimizelyUserContext, ruleId: string, options: DecideOptionsMap, + ): Promise<CmabDecision> { + const serializerIndex = this.getSerializerIndex(userContext.getUserId(), ruleId); + return this.serializers[serializerIndex].run(() => + this.getDecisionInternal(projectConfig, userContext, ruleId, options) + ); + } + + private async getDecisionInternal( + projectConfig: ProjectConfig, + userContext: IOptimizelyUserContext, + ruleId: string, + options: DecideOptionsMap, ): Promise<CmabDecision> { const filteredAttributes = this.filterAttributes(projectConfig, userContext, ruleId); diff --git a/lib/utils/executor/serial_runner.spec.ts b/lib/utils/executor/serial_runner.spec.ts new file mode 100644 index 000000000..6456735a9 --- /dev/null +++ b/lib/utils/executor/serial_runner.spec.ts @@ -0,0 +1,143 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach } from 'vitest'; + +import { SerialRunner } from './serial_runner'; +import { resolvablePromise } from '../promise/resolvablePromise'; +import { exhaustMicrotasks } from '../../tests/testUtils'; + +describe('SerialRunner', () => { + let serialRunner: SerialRunner; + + beforeEach(() => { + serialRunner = new SerialRunner(); + }); + + it('should return result from a single async function', async () => { + const fn = () => Promise.resolve('result'); + + const result = await serialRunner.run(fn); + + expect(result).toBe('result'); + }); + + it('should reject with same error when the passed function rejects', async () => { + const error = new Error('test error'); + const fn = () => Promise.reject(error); + + await expect(serialRunner.run(fn)).rejects.toThrow(error); + }); + + it('should execute multiple async functions in order', async () => { + const executionOrder: number[] = []; + const promises = [resolvablePromise(), resolvablePromise(), resolvablePromise()]; + + const createTask = (id: number) => async () => { + executionOrder.push(id); + await promises[id]; + return id; + }; + + const results = [serialRunner.run(createTask(0)), serialRunner.run(createTask(1)), serialRunner.run(createTask(2))]; + + // only first task should have started + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0]); + + // Resolve first task - second should start + promises[0].resolve(''); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1]); + + // Resolve second task - third should start + promises[1].resolve(''); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1, 2]); + + // Resolve third task - all done + promises[2].resolve(''); + + // Verify all results are correct + expect(await results[0]).toBe(0); + expect(await results[1]).toBe(1); + expect(await results[2]).toBe(2); + }); + + it('should continue execution even if one function throws an error', async () => { + const executionOrder: number[] = []; + const promises = [resolvablePromise(), resolvablePromise(), resolvablePromise()]; + + const createTask = (id: number) => async () => { + executionOrder.push(id); + await promises[id]; + return id; + }; + + const results = [serialRunner.run(createTask(0)), serialRunner.run(createTask(1)), serialRunner.run(createTask(2))]; + + // only first task should have started + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0]); + + // reject first task - second should still start + promises[0].reject(new Error('first error')); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1]); + + // reject second task - third should still start + promises[1].reject(new Error('second error')); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1, 2]); + + // Resolve third task - all done + promises[2].resolve(''); + + // Verify results - first and third succeed, second fails + await expect(results[0]).rejects.toThrow('first error'); + await expect(results[1]).rejects.toThrow('second error'); + await expect(results[2]).resolves.toBe(2); + }); + + it('should handle functions that return different types', async () => { + const numberFn = () => Promise.resolve(42); + const stringFn = () => Promise.resolve('hello'); + const objectFn = () => Promise.resolve({ key: 'value' }); + const arrayFn = () => Promise.resolve([1, 2, 3]); + const booleanFn = () => Promise.resolve(true); + const nullFn = () => Promise.resolve(null); + const undefinedFn = () => Promise.resolve(undefined); + + const results = await Promise.all([ + serialRunner.run(numberFn), + serialRunner.run(stringFn), + serialRunner.run(objectFn), + serialRunner.run(arrayFn), + serialRunner.run(booleanFn), + serialRunner.run(nullFn), + serialRunner.run(undefinedFn), + ]); + + expect(results).toEqual([42, 'hello', { key: 'value' }, [1, 2, 3], true, null, undefined]); + }); + + it('should handle empty function that returns undefined', async () => { + const emptyFn = () => Promise.resolve(undefined); + + const result = await serialRunner.run(emptyFn); + + expect(result).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/lib/utils/executor/serial_runner.ts b/lib/utils/executor/serial_runner.ts new file mode 100644 index 000000000..243cae0b1 --- /dev/null +++ b/lib/utils/executor/serial_runner.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AsyncProducer } from "../type"; + +class SerialRunner { + private waitPromise: Promise<unknown> = Promise.resolve(); + + // each call to serialize adds a new function to the end of the promise chain + // the function is called when the previous promise resolves + // if the function throws, the error is caught and ignored to allow the chain to continue + // the result of the function is returned as a promise + // if multiple calls to serialize are made, they will be executed in order + // even if some of them throw errors + + run<T>(fn: AsyncProducer<T>): Promise<T> { + const resultPromise = this.waitPromise.then(fn); + this.waitPromise = resultPromise.catch(() => {}); + return resultPromise; + } +} + +export { SerialRunner }; From 99a4ca724750e2c3e1e2145ada04bf3a6d6bbf29 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Tue, 7 Oct 2025 20:25:23 +0600 Subject: [PATCH 194/200] [FSSDK-11879] flush events without closing client on page unload (#1087) Currently, in browser, when the page is unloaded, the sdk instance is being closed in order to flush pending events. But when the page is loaded from [bfcache](https://developer.mozilla.org/en-US/docs/Glossary/bfcache), the sdk instance stays closed as bfcache restores the Javascript heap as well, which causes further events to be not processed. This PR updates the code to flush events on page unload without closing the sdk instance. --- lib/client_factory.ts | 2 +- .../batch_event_processor.spec.ts | 86 +++++++++++++++++-- lib/event_processor/batch_event_processor.ts | 11 ++- .../event_builder/log_event.ts | 2 +- lib/event_processor/event_processor.ts | 1 + .../forwarding_event_processor.ts | 4 + lib/index.browser.ts | 2 +- .../event_manager/odp_event_manager.spec.ts | 41 +++++++++ lib/odp/event_manager/odp_event_manager.ts | 8 ++ lib/odp/odp_manager.spec.ts | 24 ++++++ lib/odp/odp_manager.ts | 8 ++ lib/optimizely/index.spec.ts | 33 +++++++ lib/optimizely/index.ts | 18 ++++ package-lock.json | 11 +-- package.json | 2 - 15 files changed, 230 insertions(+), 23 deletions(-) diff --git a/lib/client_factory.ts b/lib/client_factory.ts index 7307075cf..87a239246 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -31,7 +31,7 @@ export type OptimizelyFactoryConfig = Config & { requestHandler: RequestHandler; } -export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client => { +export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Optimizely => { const { clientEngine, clientVersion, diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts index a95dd262f..6d7674fd5 100644 --- a/lib/event_processor/batch_event_processor.spec.ts +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -875,7 +875,7 @@ describe('BatchEventProcessor', async () => { }); describe('retryFailedEvents', () => { - it('should disptach only failed events from the store and not dispatch queued events', async () => { + it('should dispatch only failed events from the store and not dispatch queued events', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -921,7 +921,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach only failed events from the store and not dispatch events that are being dispatched', async () => { + it('should dispatch only failed events from the store and not dispatch events that are being dispatched', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; const mockResult1 = resolvablePromise(); @@ -977,7 +977,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach events in correct batch size and separate events with differnt contexts in separate batch', async () => { + it('should dispatch events in correct batch size and separate events with differnt contexts in separate batch', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -1023,7 +1023,7 @@ describe('BatchEventProcessor', async () => { }); describe('when failedEventRepeater is fired', () => { - it('should disptach only failed events from the store and not dispatch queued events', async () => { + it('should dispatch only failed events from the store and not dispatch queued events', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -1071,7 +1071,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach only failed events from the store and not dispatch events that are being dispatched', async () => { + it('should dispatch only failed events from the store and not dispatch events that are being dispatched', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; const mockResult1 = resolvablePromise(); @@ -1129,7 +1129,7 @@ describe('BatchEventProcessor', async () => { ])); }); - it('should disptach events in correct batch size and separate events with differnt contexts in separate batch', async () => { + it('should dispatch events in correct batch size and separate events with differnt contexts in separate batch', async () => { const eventDispatcher = getMockDispatcher(); const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; mockDispatch.mockResolvedValue({}); @@ -1277,7 +1277,7 @@ describe('BatchEventProcessor', async () => { expect(failedEventRepeater.stop).toHaveBeenCalledOnce(); }); - it('should disptach the events in queue using the closing dispatcher if available', async () => { + it('should dispatch the events in queue using the closing dispatcher if available', async () => { const eventDispatcher = getMockDispatcher(); const closingEventDispatcher = getMockDispatcher(); closingEventDispatcher.dispatchEvent.mockResolvedValue({}); @@ -1408,4 +1408,76 @@ describe('BatchEventProcessor', async () => { await expect(processor.onTerminated()).resolves.not.toThrow(); }); }); + + describe('flushImmediately', () => { + it('should dispatch the events in queue using the closing dispatcher if available', async () => { + const eventDispatcher = getMockDispatcher(); + const closingEventDispatcher = getMockDispatcher(); + closingEventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + closingEventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.flushImmediately(); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + + expect(processor.isRunning()).toBe(true); + }); + + + it('should dispatch the events in queue using eventDispatcher if closingEventDispatcher is not available', async () => { + const eventDispatcher = getMockDispatcher(); + eventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.flushImmediately(); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + + expect(processor.isRunning()).toBe(true); + }); + }); }); diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index 6ad19eaf8..86f7ff148 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -230,14 +230,14 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { }); } - private async flush(closing = false): Promise<void> { + private async flush(useClosingDispatcher = false): Promise<void> { const batch = this.createNewBatch(); if (!batch) { return; } this.dispatchRepeater.reset(); - this.dispatchBatch(batch, closing); + this.dispatchBatch(batch, useClosingDispatcher); } async process(event: ProcessableEvent): Promise<void> { @@ -332,6 +332,13 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } } + flushImmediately(): Promise<unknown> { + if (!this.isRunning()) { + return Promise.resolve(); + } + return this.flush(true); + } + stop(): void { if (this.isDone()) { return; diff --git a/lib/event_processor/event_builder/log_event.ts b/lib/event_processor/event_builder/log_event.ts index d3ec940fa..4d4048950 100644 --- a/lib/event_processor/event_builder/log_event.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -222,7 +222,7 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { export function buildLogEvent(events: UserEvent[]): LogEvent { const region = events[0]?.context.region || 'US'; - const url = logxEndpoint[region]; + const url = logxEndpoint[region] || logxEndpoint['US']; return { url, diff --git a/lib/event_processor/event_processor.ts b/lib/event_processor/event_processor.ts index 3589ce3a5..585c71f68 100644 --- a/lib/event_processor/event_processor.ts +++ b/lib/event_processor/event_processor.ts @@ -28,4 +28,5 @@ export interface EventProcessor extends Service { process(event: ProcessableEvent): Promise<unknown>; onDispatch(handler: Consumer<LogEvent>): Fn; setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise<unknown>; } diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index 80ce1c763..f578992c7 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -69,4 +69,8 @@ export class ForwardingEventProcessor extends BaseService implements EventProces onDispatch(handler: Consumer<LogEvent>): Fn { return this.eventEmitter.on('dispatch', handler); } + + flushImmediately(): Promise<unknown> { + return Promise.resolve(); + } } diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 4cbfc7c69..0f644a844 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -37,7 +37,7 @@ export const createInstance = function(config: Config): Client { window.addEventListener( unloadEvent, () => { - client.close(); + client.flushImmediately(); }, ); } diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index 6fb5db08a..68484d788 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -1010,4 +1010,45 @@ describe('DefaultOdpEventManager', () => { await expect(odpEventManager.onTerminated()).resolves.not.toThrow(); expect(odpEventManager.getState()).toBe(ServiceState.Terminated); }); + + it('should flush the queue when flushImmediately() is called in running state', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + odpEventManager.flushImmediately(); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenCalledWith(config, events); + expect(odpEventManager.isRunning()).toBe(true); + }); }); diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 3a9c591cc..d1a30d3ff 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -42,6 +42,7 @@ export interface OdpEventManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; sendEvent(event: OdpEvent): void; setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise<unknown>; } export type RetryConfig = { @@ -160,6 +161,13 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag this.startPromise.resolve(); } + flushImmediately(): Promise<unknown> { + if (!this.isRunning()) { + return Promise.resolve(); + } + return this.flush(); + } + stop(): void { if (this.isDone()) { return; diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index 376a663cf..9ae0daf69 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -53,6 +53,7 @@ const getMockOdpEventManager = () => { sendEvent: vi.fn(), makeDisposable: vi.fn(), setLogger: vi.fn(), + flushImmediately: vi.fn(), }; }; @@ -780,6 +781,29 @@ describe('DefaultOdpManager', () => { odpManager.makeDisposable(); expect(eventManager.makeDisposable).toHaveBeenCalled(); + }); + + it('should call flushImmediately() on eventManager when flushImmediately() is called on odpManager', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockResolvedValue({}); + const segmentManager = getMockOdpSegmentManager(); + + eventManager.flushImmediately.mockResolvedValue({}); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + odpManager.start(); + + await odpManager.onRunning(); + + odpManager.flushImmediately(); + + expect(eventManager.flushImmediately).toHaveBeenCalledOnce(); + expect(odpManager.isRunning()).toBe(true); }) }); diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 2f8256c38..feaca24b9 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -40,6 +40,7 @@ export interface OdpManager extends Service { setClientInfo(clientEngine: string, clientVersion: string): void; setVuid(vuid: string): void; setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise<unknown>; } export type OdpManagerConfig = { @@ -145,6 +146,13 @@ export class DefaultOdpManager extends BaseService implements OdpManager { this.stopPromise.reject(error); } + flushImmediately(): Promise<unknown> { + if (!this.isRunning()) { + return Promise.resolve(); + } + return this.eventManager.flushImmediately(); + } + stop(): void { if (this.isDone()) { return; diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts index 81509fd1e..4548ffbb7 100644 --- a/lib/optimizely/index.spec.ts +++ b/lib/optimizely/index.spec.ts @@ -872,4 +872,37 @@ describe('Optimizely', () => { }); }); }); + + it('should flush eventProcessor and odpManager on flushImmediately()', async () => { + const projectConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const odpManager = extractOdpManager(createOdpManager({})); + + const optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + odpManager?.updateConfig({ integrated: false }); + await optimizely.onReady(); + + const eventProcessorFlushSpy = vi.spyOn(eventProcessor, 'flushImmediately').mockResolvedValue(Promise.resolve()); + const odpManagerFlushSpy = vi.spyOn(odpManager!, 'flushImmediately').mockResolvedValue(Promise.resolve()); + + await optimizely.flushImmediately(); + + expect(eventProcessorFlushSpy).toHaveBeenCalled(); + expect(odpManagerFlushSpy).toHaveBeenCalled(); + + expect(optimizely.isRunning()).toBe(true); + }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index f6e2b4f35..b8707a006 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1243,6 +1243,24 @@ export default class Optimizely extends BaseService implements Client { } } + flushImmediately(): Promise<unknown> { + const flushPromises = []; + + if (!this.isRunning()) { + return Promise.resolve(); + } + + if (this.eventProcessor) { + flushPromises.push(this.eventProcessor.flushImmediately()); + } + + if(this.odpManager) { + flushPromises.push(this.odpManager.flushImmediately()); + } + + return Promise.all(flushPromises); + } + /** * Stop background processes belonging to this instance, including: * diff --git a/package-lock.json b/package-lock.json index 4e6e462c9..4820c783c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "eslint-plugin-prettier": "^3.1.2", "happy-dom": "^16.6.0", "jiti": "^2.4.1", - "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", "karma-chai": "^0.1.0", @@ -57,7 +56,6 @@ "ts-loader": "^9.3.1", "ts-node": "^8.10.2", "tsconfig-paths": "^4.2.0", - "tslib": "^2.4.0", "typescript": "^4.7.4", "vitest": "^2.0.5", "webpack": "^5.74.0" @@ -9109,12 +9107,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -13685,7 +13677,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index 9aa609e6c..675bb7d4f 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,6 @@ "eslint-plugin-prettier": "^3.1.2", "happy-dom": "^16.6.0", "jiti": "^2.4.1", - "json-loader": "^0.5.4", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", "karma-chai": "^0.1.0", @@ -139,7 +138,6 @@ "ts-loader": "^9.3.1", "ts-node": "^8.10.2", "tsconfig-paths": "^4.2.0", - "tslib": "^2.4.0", "typescript": "^4.7.4", "vitest": "^2.0.5", "webpack": "^5.74.0" From e779889c32273c651293071ae077e81f91633995 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Wed, 15 Oct 2025 00:16:46 +0600 Subject: [PATCH 195/200] fix typo in clientEngine option (#1095) --- lib/index.node.ts | 6 +++--- lib/index.react_native.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/index.node.ts b/lib/index.node.ts index cd12b25fb..02d162ed6 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -15,7 +15,7 @@ */ import { NODE_CLIENT_ENGINE } from './utils/enums'; import { Client, Config } from './shared_types'; -import { getOptimizelyInstance } from './client_factory'; +import { getOptimizelyInstance, OptimizelyFactoryConfig } from './client_factory'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; import { NodeRequestHandler } from './utils/http_request_handler/request_handler.node'; @@ -26,9 +26,9 @@ import { NodeRequestHandler } from './utils/http_request_handler/request_handler * null on error */ export const createInstance = function(config: Config): Client { - const nodeConfig = { + const nodeConfig: OptimizelyFactoryConfig = { ...config, - clientEnging: config.clientEngine || NODE_CLIENT_ENGINE, + clientEngine: config.clientEngine || NODE_CLIENT_ENGINE, requestHandler: new NodeRequestHandler(), } diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index a556290ba..c393261b7 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -17,7 +17,7 @@ import 'fast-text-encoding'; import 'react-native-get-random-values'; import { Client, Config } from './shared_types'; -import { getOptimizelyInstance } from './client_factory'; +import { getOptimizelyInstance, OptimizelyFactoryConfig } from './client_factory'; import { REACT_NATIVE_JS_CLIENT_ENGINE } from './utils/enums'; import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser'; @@ -29,7 +29,7 @@ import { BrowserRequestHandler } from './utils/http_request_handler/request_hand * null on error */ export const createInstance = function(config: Config): Client { - const rnConfig = { + const rnConfig: OptimizelyFactoryConfig = { ...config, clientEngine: config.clientEngine || REACT_NATIVE_JS_CLIENT_ENGINE, requestHandler: new BrowserRequestHandler(), From bfdcfad9f9086c875a6d2388d437dd3acf67582f Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Fri, 17 Oct 2025 18:43:03 +0600 Subject: [PATCH 196/200] [FSSDK-11979] add cmab cache configuration options to public api (#1096) --- lib/client_factory.ts | 13 ++- lib/shared_types.ts | 6 ++ lib/utils/cache/cache.spec.ts | 170 ++++++++++++++++++++++++++++++++++ lib/utils/cache/cache.ts | 36 ++++++- 4 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 lib/utils/cache/cache.spec.ts diff --git a/lib/client_factory.ts b/lib/client_factory.ts index 87a239246..f1534d786 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Client, Config } from "./shared_types"; +import { Config } from "./shared_types"; import { extractLogger } from "./logging/logger_factory"; import { extractErrorNotifier } from "./error/error_notifier_factory"; import { extractConfigManager } from "./project_config/config_manager_factory"; @@ -26,6 +26,7 @@ import Optimizely from "./optimizely"; import { DefaultCmabClient } from "./core/decision_service/cmab/cmab_client"; import { CmabCacheValue, DefaultCmabService } from "./core/decision_service/cmab/cmab_service"; import { InMemoryLruCache } from "./utils/cache/in_memory_lru_cache"; +import { transformCache, CacheWithRemove } from "./utils/cache/cache"; export type OptimizelyFactoryConfig = Config & { requestHandler: RequestHandler; @@ -54,9 +55,17 @@ export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Optimize requestHandler, }); + const cmabCache: CacheWithRemove<CmabCacheValue> = config.cmab?.cache ? + transformCache(config.cmab.cache, (value) => JSON.parse(value), (value) => JSON.stringify(value)) : + (() => { + const cacheSize = config.cmab?.cacheSize || DEFAULT_CMAB_CACHE_SIZE; + const cacheTtl = config.cmab?.cacheTtl || DEFAULT_CMAB_CACHE_TIMEOUT; + return new InMemoryLruCache<CmabCacheValue>(cacheSize, cacheTtl); + })(); + const cmabService = new DefaultCmabService({ cmabClient, - cmabCache: new InMemoryLruCache<CmabCacheValue>(DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT), + cmabCache, }); const optimizelyOptions = { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 45e66413b..ef4221db3 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -45,6 +45,7 @@ import { OpaqueErrorNotifier } from './error/error_notifier_factory'; import { OpaqueEventProcessor } from './event_processor/event_processor_factory'; import { OpaqueOdpManager } from './odp/odp_manager_factory'; import { OpaqueVuidManager } from './vuid/vuid_manager_factory'; +import { CacheWithRemove } from './utils/cache/cache'; export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; export { EventProcessor } from './event_processor/event_processor'; @@ -397,6 +398,11 @@ export interface Config { odpManager?: OpaqueOdpManager; vuidManager?: OpaqueVuidManager; disposable?: boolean; + cmab?: { + cacheSize?: number; + cacheTtl?: number; + cache?: CacheWithRemove<string>; + } } export type OptimizelyExperimentsMap = { diff --git a/lib/utils/cache/cache.spec.ts b/lib/utils/cache/cache.spec.ts new file mode 100644 index 000000000..687176d59 --- /dev/null +++ b/lib/utils/cache/cache.spec.ts @@ -0,0 +1,170 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { transformCache, SyncCacheWithRemove, AsyncCacheWithRemove, CacheWithRemove } from './cache'; +import { getMockSyncCache, getMockAsyncCache } from '../../tests/mock/mock_cache'; + +const numberToString = (value: number): string => value.toString(); +const stringToNumber = (value: string): number => parseInt(value, 10); + +describe('transformCache', () => { + describe('sync cache operations', () => { + let mockCache: SyncCacheWithRemove<string>; + let transformedCache: CacheWithRemove<number>; + + beforeEach(() => { + mockCache = getMockSyncCache<string>(); + transformedCache = transformCache(mockCache, stringToNumber, numberToString); + }); + + it('should transform and save values using transformSet', () => { + const value = 42; + const key = 'test-key'; + + transformedCache.save(key, value); + + expect(mockCache.lookup(key)).toBe('42'); + }); + + it('should lookup and transform values using transformGet when value exists', () => { + const key = 'test-key'; + const storedValue = '123'; + + mockCache.save(key, storedValue); + + const result = transformedCache.lookup(key); + expect(result).toBe(123); + }); + + it('should return undefined when lookup value does not exist', () => { + const key = 'non-existent-key'; + + const result = transformedCache.lookup(key); + expect(result).toBeUndefined(); + }); + + it('should return the saved value on lookup', () => { + const key = 'sample-key'; + const value = 100; + + transformedCache.save(key, value); + const result = transformedCache.lookup(key); + expect(result).toBe(value); + }); + + it('should reset the cache when reset is called', () => { + transformedCache.save('key1', 42); + transformedCache.save('key2', 1729); + + transformedCache.reset(); + + expect(mockCache.lookup('key1')).toBeUndefined(); + expect(mockCache.lookup('key2')).toBeUndefined(); + expect(transformedCache.lookup('key1')).toBeUndefined(); + expect(transformedCache.lookup('key2')).toBeUndefined(); + }); + + it('should remove the key from the cache when remove is called', () => { + const key = 'test-key'; + transformedCache.save(key, 99); + + expect(transformedCache.lookup(key)).toBe(99); + + transformedCache.remove(key); + expect(mockCache.lookup(key)).toBeUndefined(); + expect(transformedCache.lookup(key)).toBeUndefined(); + }); + + it('should preserve original cache operation type', () => { + expect(transformedCache.operation).toBe('sync'); + }); + }); + + describe('async cache operations', () => { + let mockAsyncCache: AsyncCacheWithRemove<string>; + let transformedAsyncCache: CacheWithRemove<number>; + + beforeEach(() => { + mockAsyncCache = getMockAsyncCache<string>(); + transformedAsyncCache = transformCache(mockAsyncCache, stringToNumber, numberToString); + }); + + it('should transform and save values using transformSet', async () => { + const value = 42; + const key = 'test-key'; + + await transformedAsyncCache.save(key, value); + + const result = await mockAsyncCache.lookup(key); + expect(result).toBe('42'); + }); + + it('should lookup and transform values using transformGet when value exists', async () => { + const key = 'test-key'; + const storedValue = '123'; + + await mockAsyncCache.save(key, storedValue); + + const result = await transformedAsyncCache.lookup(key); + expect(result).toBe(123); + }); + + it('should return undefined when lookup value does not exist', async () => { + const key = 'non-existent-key'; + + const result = await transformedAsyncCache.lookup(key); + expect(result).toBeUndefined(); + }); + + it('should return the saved value on lookup', async () => { + const key = 'sample-key'; + const value = 100; + + await transformedAsyncCache.save(key, value); + const result = await transformedAsyncCache.lookup(key); + expect(result).toBe(value); + }); + + it('should reset the cache when reset is called', async () => { + await transformedAsyncCache.save('key1', 42); + await transformedAsyncCache.save('key2', 1729); + + await transformedAsyncCache.reset(); + + await expect(mockAsyncCache.lookup('key1')).resolves.toBeUndefined(); + await expect(mockAsyncCache.lookup('key2')).resolves.toBeUndefined(); + + await expect(transformedAsyncCache.lookup('key1')).resolves.toBeUndefined(); + await expect(transformedAsyncCache.lookup('key2')).resolves.toBeUndefined(); + }); + + it('should remove the key from the cache when remove is called', async () => { + const key = 'test-key'; + await transformedAsyncCache.save(key, 99); + + await expect(transformedAsyncCache.lookup(key)).resolves.toBe(99); + + await transformedAsyncCache.remove(key); + + await expect(mockAsyncCache.lookup(key)).resolves.toBeUndefined(); + await expect(transformedAsyncCache.lookup(key)).resolves.toBeUndefined(); + }); + + it('should preserve original cache operation type', () => { + expect(transformedAsyncCache.operation).toBe('async'); + }); + }); +}); \ No newline at end of file diff --git a/lib/utils/cache/cache.ts b/lib/utils/cache/cache.ts index ada8a5ac6..685b43a7b 100644 --- a/lib/utils/cache/cache.ts +++ b/lib/utils/cache/cache.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { OpType, OpValue } from '../../utils/type'; - +import { Transformer } from '../../utils/type'; export interface OpCache<OP extends OpType, V> { operation: OP; save(key: string, value: V): OpValue<OP, unknown>; @@ -34,3 +34,37 @@ export interface OpCacheWithRemove<OP extends OpType, V> extends OpCache<OP, V> export type SyncCacheWithRemove<V> = OpCacheWithRemove<'sync', V>; export type AsyncCacheWithRemove<V> = OpCacheWithRemove<'async', V>; export type CacheWithRemove<V> = SyncCacheWithRemove<V> | AsyncCacheWithRemove<V>; + +export const transformCache = <U, V> ( + cache: CacheWithRemove<U>, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U>, +): CacheWithRemove<V> => { + const transform = <U, V>(value: U | undefined, transformer: Transformer<U, V>): V | undefined => { + if (value === undefined) { + return undefined; + } + return transformer(value); + } + + const lookup: any = (key: string) => { + if (cache.operation === 'sync') { + return transform(cache.lookup(key), transformGet); + } + + return cache.lookup(key).then((v) => transform(v, transformGet)); + } + + const save: any = (key: string, value: V) => { + return cache.save(key, transformSet(value)); + } + + const transformedCache = { + lookup, + save, + }; + + Object.setPrototypeOf(transformedCache, cache); + + return transformedCache as CacheWithRemove<V>; +}; From 9c75ee1bf2729be2fc8ac5e3860130074e1ac619 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:11:31 +0600 Subject: [PATCH 197/200] [FSSDK-11981] bug bash addressed (#1097) --- lib/client_factory.ts | 1 + .../decision_service/cmab/cmab_service.ts | 26 ++++++++++++++++--- lib/index.browser.tests.js | 1 + lib/message/log_message.ts | 6 +++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/client_factory.ts b/lib/client_factory.ts index f1534d786..f8588c33c 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -66,6 +66,7 @@ export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Optimize const cmabService = new DefaultCmabService({ cmabClient, cmabCache, + logger: logger?.child() }); const optimizelyOptions = { diff --git a/lib/core/decision_service/cmab/cmab_service.ts b/lib/core/decision_service/cmab/cmab_service.ts index cd3ab99ea..1963df613 100644 --- a/lib/core/decision_service/cmab/cmab_service.ts +++ b/lib/core/decision_service/cmab/cmab_service.ts @@ -18,12 +18,20 @@ import { LoggerFacade } from "../../../logging/logger"; import { IOptimizelyUserContext } from "../../../optimizely_user_context"; import { ProjectConfig } from "../../../project_config/project_config" import { OptimizelyDecideOption, UserAttributes } from "../../../shared_types" -import { Cache, CacheWithRemove } from "../../../utils/cache/cache"; +import { CacheWithRemove } from "../../../utils/cache/cache"; import { CmabClient } from "./cmab_client"; import { v4 as uuidV4 } from 'uuid'; import murmurhash from "murmurhash"; import { DecideOptionsMap } from ".."; import { SerialRunner } from "../../../utils/executor/serial_runner"; +import { + CMAB_CACHE_ATTRIBUTES_MISMATCH, + CMAB_CACHE_HIT, + CMAB_CACHE_MISS, + IGNORE_CMAB_CACHE, + INVALIDATE_CMAB_CACHE, + RESET_CMAB_CACHE, +} from 'log_message'; export type CmabDecision = { variationId: string, @@ -59,6 +67,7 @@ export type CmabServiceOptions = { } const SERIALIZER_BUCKETS = 1000; +const LOGGER_NAME = 'CmabService'; export class DefaultCmabService implements CmabService { private cmabCache: CacheWithRemove<CmabCacheValue>; @@ -72,6 +81,7 @@ export class DefaultCmabService implements CmabService { this.cmabCache = options.cmabCache; this.cmabClient = options.cmabClient; this.logger = options.logger; + this.logger?.setName(LOGGER_NAME); } private getSerializerIndex(userId: string, experimentId: string): number { @@ -98,19 +108,23 @@ export class DefaultCmabService implements CmabService { ruleId: string, options: DecideOptionsMap, ): Promise<CmabDecision> { + const userId = userContext.getUserId(); const filteredAttributes = this.filterAttributes(projectConfig, userContext, ruleId); if (options[OptimizelyDecideOption.IGNORE_CMAB_CACHE]) { - return this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes); + this.logger?.debug(IGNORE_CMAB_CACHE, userId, ruleId); + return this.fetchDecision(ruleId, userId, filteredAttributes); } if (options[OptimizelyDecideOption.RESET_CMAB_CACHE]) { + this.logger?.debug(RESET_CMAB_CACHE, userId, ruleId); this.cmabCache.reset(); } - const cacheKey = this.getCacheKey(userContext.getUserId(), ruleId); + const cacheKey = this.getCacheKey(userId, ruleId); if (options[OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]) { + this.logger?.debug(INVALIDATE_CMAB_CACHE, userId, ruleId); this.cmabCache.remove(cacheKey); } @@ -121,13 +135,17 @@ export class DefaultCmabService implements CmabService { if (cachedValue) { if (cachedValue.attributesHash === attributesHash) { + this.logger?.debug(CMAB_CACHE_HIT, userId, ruleId); return { variationId: cachedValue.variationId, cmabUuid: cachedValue.cmabUuid }; } else { + this.logger?.debug(CMAB_CACHE_ATTRIBUTES_MISMATCH, userId, ruleId); this.cmabCache.remove(cacheKey); } + } else { + this.logger?.debug(CMAB_CACHE_MISS, userId, ruleId); } - const variation = await this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes); + const variation = await this.fetchDecision(ruleId, userId, filteredAttributes); this.cmabCache.save(cacheKey, { attributesHash, variationId: variation.variationId, diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 8b2e93e7f..c12ed8ef8 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -67,6 +67,7 @@ var getLogger = () => ({ warn: () => {}, error: () => {}, child: () => getLogger(), + setName: () => {}, }) describe('javascript-sdk (Browser)', function() { diff --git a/lib/message/log_message.ts b/lib/message/log_message.ts index c27f5076f..aaf5a9e36 100644 --- a/lib/message/log_message.ts +++ b/lib/message/log_message.ts @@ -61,5 +61,11 @@ export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = export const INVALID_EXPERIMENT_KEY_INFO = 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; export const EVENT_STORE_FULL = 'Event store is full. Not saving event with id %d.'; +export const IGNORE_CMAB_CACHE = 'Ignoring CMAB cache for user %s and rule %s.'; +export const RESET_CMAB_CACHE = 'Resetting CMAB cache for user %s and rule %s.'; +export const INVALIDATE_CMAB_CACHE = 'Invalidating CMAB cache for user %s and rule %s.'; +export const CMAB_CACHE_HIT = 'Cache hit for user %s and rule %s.'; +export const CMAB_CACHE_ATTRIBUTES_MISMATCH = 'CMAB cache attributes mismatch for user %s and rule %s, fetching new decision.'; +export const CMAB_CACHE_MISS = 'Cache miss for user %s and rule %s.'; export const messages: string[] = []; From f2f4c10709dd980b49d017951fb3cf175e09759b Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 23 Oct 2025 18:34:47 +0600 Subject: [PATCH 198/200] [FSSDK-11965] add retry for cmab fetch (#1099) --- lib/client_factory.ts | 9 +++++++-- lib/odp/odp_manager_factory.ts | 2 +- lib/utils/enums/index.ts | 6 ++++-- lib/utils/repeater/repeater.spec.ts | 21 +++++++++++++++++++-- lib/utils/repeater/repeater.ts | 17 ++++++++++++++++- 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/lib/client_factory.ts b/lib/client_factory.ts index f8588c33c..8bbc688ef 100644 --- a/lib/client_factory.ts +++ b/lib/client_factory.ts @@ -21,12 +21,13 @@ import { extractEventProcessor } from "./event_processor/event_processor_factory import { extractOdpManager } from "./odp/odp_manager_factory"; import { extractVuidManager } from "./vuid/vuid_manager_factory"; import { RequestHandler } from "./utils/http_request_handler/http"; -import { CLIENT_VERSION, DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums"; +import { CLIENT_VERSION, DEFAULT_CMAB_BACKOFF_MS, DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT_MS, DEFAULT_CMAB_RETRIES, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums"; import Optimizely from "./optimizely"; import { DefaultCmabClient } from "./core/decision_service/cmab/cmab_client"; import { CmabCacheValue, DefaultCmabService } from "./core/decision_service/cmab/cmab_service"; import { InMemoryLruCache } from "./utils/cache/in_memory_lru_cache"; import { transformCache, CacheWithRemove } from "./utils/cache/cache"; +import { ConstantBackoff } from "./utils/repeater/repeater"; export type OptimizelyFactoryConfig = Config & { requestHandler: RequestHandler; @@ -53,13 +54,17 @@ export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Optimize const cmabClient = new DefaultCmabClient({ requestHandler, + retryConfig: { + maxRetries: DEFAULT_CMAB_RETRIES, + backoffProvider: () => new ConstantBackoff(DEFAULT_CMAB_BACKOFF_MS), + } }); const cmabCache: CacheWithRemove<CmabCacheValue> = config.cmab?.cache ? transformCache(config.cmab.cache, (value) => JSON.parse(value), (value) => JSON.stringify(value)) : (() => { const cacheSize = config.cmab?.cacheSize || DEFAULT_CMAB_CACHE_SIZE; - const cacheTtl = config.cmab?.cacheTtl || DEFAULT_CMAB_CACHE_TIMEOUT; + const cacheTtl = config.cmab?.cacheTtl || DEFAULT_CMAB_CACHE_TIMEOUT_MS; return new InMemoryLruCache<CmabCacheValue>(cacheSize, cacheTtl); })(); diff --git a/lib/odp/odp_manager_factory.ts b/lib/odp/odp_manager_factory.ts index 9fd689964..45c79e591 100644 --- a/lib/odp/odp_manager_factory.ts +++ b/lib/odp/odp_manager_factory.ts @@ -26,7 +26,7 @@ import { DefaultOdpSegmentApiManager } from "./segment_manager/odp_segment_api_m import { DefaultOdpSegmentManager, OdpSegmentManager } from "./segment_manager/odp_segment_manager"; import { UserAgentParser } from "./ua_parser/user_agent_parser"; -export const DEFAULT_CACHE_SIZE = 1000; +export const DEFAULT_CACHE_SIZE = 10_000; export const DEFAULT_CACHE_TIMEOUT = 600_000; export const DEFAULT_EVENT_BATCH_SIZE = 100; diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 99c254e5e..fc95f2ef2 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -102,5 +102,7 @@ export const DECISION_MESSAGES = { */ export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute -export const DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 * 1000; // 30 minutes -export const DEFAULT_CMAB_CACHE_SIZE = 1000; +export const DEFAULT_CMAB_CACHE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +export const DEFAULT_CMAB_CACHE_SIZE = 10_000; +export const DEFAULT_CMAB_RETRIES = 1; +export const DEFAULT_CMAB_BACKOFF_MS = 100; // 100 milliseconds diff --git a/lib/utils/repeater/repeater.spec.ts b/lib/utils/repeater/repeater.spec.ts index e92594556..b992e0e85 100644 --- a/lib/utils/repeater/repeater.spec.ts +++ b/lib/utils/repeater/repeater.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ import { expect, vi, it, beforeEach, afterEach, describe } from 'vitest'; -import { ExponentialBackoff, IntervalRepeater } from './repeater'; +import { ConstantBackoff, ExponentialBackoff, IntervalRepeater } from './repeater'; import { advanceTimersByTime } from '../../tests/testUtils'; import { resolvablePromise } from '../promise/resolvablePromise'; @@ -88,6 +88,23 @@ describe("ExponentialBackoff", () => { }); +describe("ConstantBackoff", () => { + it("should always return the same backoff time", () => { + const constantBackoff = new ConstantBackoff(3000); + for(let i = 0; i < 5; i++) { + const time = constantBackoff.backoff(); + expect(time).toEqual(3000); + } + + // Reset to verify it still returns the same value + constantBackoff.reset(); + for(let i = 0; i < 5; i++) { + const time = constantBackoff.backoff(); + expect(time).toEqual(3000); + } + }); +}); + describe("IntervalRepeater", () => { beforeEach(() => { vi.useFakeTimers(); diff --git a/lib/utils/repeater/repeater.ts b/lib/utils/repeater/repeater.ts index 9f307ab95..829702729 100644 --- a/lib/utils/repeater/repeater.ts +++ b/lib/utils/repeater/repeater.ts @@ -1,5 +1,5 @@ /** - * Copyright 2024, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,21 @@ export class ExponentialBackoff implements BackoffController { } } +export class ConstantBackoff implements BackoffController { + private value: number; + + constructor(value: number) { + this.value = value; + } + + backoff(): number { + return this.value; + } + + reset(): void { + } +} + // IntervalRepeater is a Repeater that invokes the task at a fixed interval // after the completion of the previous task invocation. If a backoff controller // is provided, the repeater will use the backoff controller to determine the From d4fb4039b350be230fe179efb37eaa247df08a8e Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 23 Oct 2025 21:17:12 +0600 Subject: [PATCH 199/200] [FSSDK-11965] prepare release 6.2.0 (#1098) --- CHANGELOG.md | 51 ++++++++++++++++++++++++++++++++++ lib/export_types.ts | 4 +-- lib/index.browser.tests.js | 2 +- lib/index.node.tests.js | 2 +- lib/index.react_native.spec.ts | 2 +- lib/index.universal.ts | 3 +- lib/utils/enums/index.ts | 2 +- package-lock.json | 4 +-- package.json | 2 +- 9 files changed, 62 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee82dd595..9a58beb95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,57 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [6.2.0] - October 22, 2025 + +### New Features + +- **Added support for Contextual Multi-Armed Bandit (CMAB)**: Added support for CMAB experiments(Contextual Bandits rules) with new configuration options and cache control. To get decision from CMAB rules, `decideAsync` and related methods must be used. The sync `decide` method does not support CMABs and will just skip CMAB rules while making decision for a flag. + + #### CMAB Configuration Options + + The following new options have been added to configure the cmab cache: + + ```js + import { createInstance } from '@optimizely/optimizely-sdk' + + const optimizely = createInstance({ + // ... other config options + cmab: { + cacheSize: 1000, // Optional: Set CMAB cache size (default: 1000) + cacheTtl: 30 * 60 * 1000, // Optional: Set CMAB cache TTL in milliseconds (default: 30 * 60 * 1000) + cache: customCache // Optional: Custom cache implementation, instance of CacheWithRemove interface + } + }); + ``` + + #### CMAB-Related OptimizelyDecideOptions + + New decide options are available to control CMAB caching behavior: + + - `OptimizelyDecideOption.IGNORE_CMAB_CACHE`: Bypass CMAB cache for fresh decisions + - `OptimizelyDecideOption.RESET_CMAB_CACHE`: Clear and reset CMAB cache before making decisions + - `OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE`: Invalidate CMAB cache for the particular user and experiment + + ```js + + // Example usage with CMAB decide options + const decision = await userContext.decideAsync('feature-flag-key', [ + optimizelySdk.enums.OptimizelyDecideOption.IGNORE_CMAB_CACHE + ]); + ``` + +### Bug Fixes +- Flush events without closing client on page unload which causes event processing to stop working when page is loaded from bfcache ([#1087](https://github.com/optimizely/javascript-sdk/pull/1087)) +- Fixed typo in clientEngine option ([#1095](https://github.com/optimizely/javascript-sdk/pull/1095)) + +## [5.4.0] - Oct 13, 2025 + +### New Features +- Added `customHeaders` option to `datafileOptions` for passing custom HTTP headers in datafile requests ([#1092](https://github.com/optimizely/javascript-sdk/pull/1092)) +### Bug Fixes +- Fix the EventTags type to allow event properties ([#1040](https://github.com/optimizely/javascript-sdk/pull/1040)) +- Fix typo in event.experimentIds field in project config ([#1088](https://github.com/optimizely/javascript-sdk/pull/1088)) + ## [6.1.0] - September 8, 2025 ### New Features diff --git a/lib/export_types.ts b/lib/export_types.ts index ce795dbaa..b620fbb8e 100644 --- a/lib/export_types.ts +++ b/lib/export_types.ts @@ -64,8 +64,8 @@ export type { export type { ErrorHandler } from './error/error_handler'; export type { OpaqueErrorNotifier } from './error/error_notifier_factory'; -export type { Cache } from './utils/cache/cache'; -export type { Store } from './utils/cache/store' +export type { SyncCache, AsyncCache, Cache, SyncCacheWithRemove, AsyncCacheWithRemove, CacheWithRemove } from './utils/cache/cache'; +export type { SyncStore, AsyncStore, Store } from './utils/cache/store' export type { NotificationType, diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index c12ed8ef8..645739cdb 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -153,7 +153,7 @@ describe('javascript-sdk (Browser)', function() { }); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '6.1.0'); + assert.equal(optlyInstance.clientVersion, '6.2.0'); }); it('should set the JavaScript client engine and version', function() { diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 343312174..b52a0f15c 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -88,7 +88,7 @@ describe('optimizelyFactory', function() { }); assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '6.1.0'); + assert.equal(optlyInstance.clientVersion, '6.2.0'); }); // TODO: user will create and inject an event processor // these tests will be refactored accordingly diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts index 3fa5a6357..d8ed60bea 100644 --- a/lib/index.react_native.spec.ts +++ b/lib/index.react_native.spec.ts @@ -92,7 +92,7 @@ describe('javascript-sdk/react-native', () => { expect(optlyInstance).toBeInstanceOf(Optimizely); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('6.1.0'); + expect(optlyInstance.clientVersion).toEqual('6.2.0'); }); it('should set the React Native JS client engine and javascript SDK version', () => { diff --git a/lib/index.universal.ts b/lib/index.universal.ts index 9aaaa8cd3..11c39c1d1 100644 --- a/lib/index.universal.ts +++ b/lib/index.universal.ts @@ -96,7 +96,8 @@ export type { export type { ErrorHandler } from './error/error_handler'; export type { OpaqueErrorNotifier } from './error/error_notifier_factory'; -export type { Cache } from './utils/cache/cache'; +export type { SyncCache, AsyncCache, Cache, SyncCacheWithRemove, AsyncCacheWithRemove, CacheWithRemove } from './utils/cache/cache'; +export type { SyncStore, AsyncStore, Store } from './utils/cache/store' export type { NotificationType, diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index fc95f2ef2..fd76d47ec 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -41,7 +41,7 @@ export const CONTROL_ATTRIBUTES = { export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; export const NODE_CLIENT_ENGINE = 'node-sdk'; export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const CLIENT_VERSION = '6.1.0'; +export const CLIENT_VERSION = '6.2.0'; /* * Represents the source of a decision for feature management. When a feature diff --git a/package-lock.json b/package-lock.json index 4820c783c..05ce4f677 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@optimizely/optimizely-sdk", - "version": "6.1.0", + "version": "6.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@optimizely/optimizely-sdk", - "version": "6.1.0", + "version": "6.2.0", "license": "Apache-2.0", "dependencies": { "decompress-response": "^7.0.0", diff --git a/package.json b/package.json index 675bb7d4f..f094c577e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/optimizely-sdk", - "version": "6.1.0", + "version": "6.2.0", "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "main": "./dist/index.node.min.js", "browser": "./dist/index.browser.es.min.js", From 3ae698b54b30c3a9353fb8ad6a6ff699fa5b253d Mon Sep 17 00:00:00 2001 From: Raju Ahmed <raju.ahmed@optimizely.com> Date: Thu, 23 Oct 2025 21:44:49 +0600 Subject: [PATCH 200/200] update 6.2.0 release date (#1100) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a58beb95..4c3bcba29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [6.2.0] - October 22, 2025 +## [6.2.0] - October 23, 2025 ### New Features